Loading...
Loading...
Create custom Hyvä CMS component. This skill should be used when the user wants to create a new Hyvä CMS component, build a Hyvä component, or needs help with components.json and PHTML templates for Hyvä CMS. Trigger phrases include "create hyva cms component", "add cms component", "new hyva component", "build page hyva cms element", "custom cms element".
npx skill4agent add hyva-themes/hyva-ai-tools hyva-cms-componentbin/magentohyva-exec-shell-cmdAcmeCmsComponentshyva-create-moduledependencies["Hyva_CmsBase"]composer_require{"hyva-themes/commerce-module-cms": "^1.0"}app/code/vendor/Hyva_CmsBaseetc/module.xmlhyva-themes/commerce-module-cmscomposer.jsonfeature_cardhyva-cms-components-dumpiconvendor/hyva-themes/magento2-theme-module/src/view/base/web/svg/lucide/shopping-cart.svgimage.svglayout-grid.svgHyva_Theme::svg/lucide/[icon-name].svgiconreferences/field-types.mdreferences/field-types.mdattributes.requiredattributesreferences/variant-support.mdhyva-create-moduleapp/code/[Vendor]/[Module]/
├── registration.php # Created by hyva-create-module
├── composer.json # Created by hyva-create-module
├── etc/
│ ├── module.xml # Created by hyva-create-module
│ └── hyva_cms/
│ └── components.json # Create this
└── view/
└── frontend/
└── templates/
└── elements/
└── [component-name].phtml (or [component-name]/ for variants)etc/hyva_cms/components.jsonview/frontend/templates/elements/[component-name].phtmlbin/magento setup:upgradehyva-exec-shell-cmd{
"[component_name]": {
"label": "[Label]",
"category": "[Category]",
"template": "[Vendor]_[Module]::elements/[component-name].phtml",
"content": {
// Generated fields
},
"design": {
"includes": [
"Hyva_CmsBase::etc/hyva_cms/default_design.json",
"Hyva_CmsBase::etc/hyva_cms/default_design_typography.json"
]
},
"advanced": {
"includes": [
"Hyva_CmsBase::etc/hyva_cms/default_advanced.json"
]
}
}
}references/component-schema.mdlabelcategorytemplateiconchildrenrequire_parentcontentdesignadvanceddisabledcustom_propertieshiddenrequire_parent: truedisabled: truechildrencontentdesignadvanced{
"my_component": {
"content": {
"items": {
"type": "children",
"label": "Items"
}
}
}
}{
"my_component": {
"label": "My Component",
"children": {
"config": {
"accepts": ["child_component"],
"max_children": 10
}
},
"content": {
"title": {
"type": "text",
"label": "Title"
}
}
}
}$block->getData('children')requiredattributes{
"title": {
"type": "text",
"label": "Title",
"required": true
}
}{
"title": {
"type": "text",
"label": "Title",
"attributes": {
"required": true
}
}
}attributesrequiredminlengthmaxlengthminmaxpatternplaceholdercommentrequire_parent: true{
"my_list_item": {
"label": "My List Item",
"category": "Elements",
"require_parent": true,
"template": false,
"content": {
"title": {"type": "text", "label": "Title"}
}
},
"my_list": {
"label": "My List",
"category": "Elements",
"template": "Vendor_Module::elements/my-list.phtml",
"children": {
"config": {
"accepts": ["my_list_item"]
}
}
}
}template: false$block->createChildHtml()<?php
declare(strict_types=1);
use Hyva\CmsLiveviewEditor\Block\Element;
use Hyva\Theme\Model\ViewModelRegistry;
use Magento\Framework\Escaper;
/** @var Element $block */
/** @var Escaper $escaper */
/** @var ViewModelRegistry $viewModels */$block->getEditorAttrs()$block->getEditorAttrs('field_name')$escaper->escapeHtml()$escaper->escapeHtmlAttr()$title = $block->getData('title');
// In template:
<?php if ($title): ?>
<h2 <?= /** @noEscape */ $block->getEditorAttrs('title') ?>>
<?= $escaper->escapeHtml($title) ?>
</h2>
<?php endif; ?>$content = $block->getData('content');
// In template (no escaping for richtext):
<?php if ($content): ?>
<div <?= /** @noEscape */ $block->getEditorAttrs('content') ?>>
<?= /** @noEscape */ $content ?>
</div>
<?php endif; ?>hyva-render-media-image\Hyva\Theme\ViewModel\Media// Additional imports for templates with images:
use Hyva\Theme\ViewModel\Media;
/** @var Media $mediaViewModel */
$mediaViewModel = $viewModels->require(Media::class);$block->getData('image')getResponsivePictureHtml()$image = $block->getData('image');
// In template:
<?php if ($image): ?>
<?= /** @noEscape */ $mediaViewModel->getResponsivePictureHtml(
$image,
['class' => 'w-full h-auto', 'loading' => 'lazy']
) ?>
<?php endif; ?>hyva-render-media-image$link = $block->getData('link');
$linkData = $link ? $block->getLinkData($link) : null;
// In template:
<?php if ($linkData): ?>
<a href="<?= $escaper->escapeUrl($linkData['url']) ?>"
<?php if (!empty($linkData['target'])): ?>target="<?= $escaper->escapeHtmlAttr($linkData['target']) ?>"<?php endif; ?>>
<?= $escaper->escapeHtml($linkData['title'] ?: 'Read more') ?>
</a>
<?php endif; ?>$showTitle = (bool) $block->getData('show_title');
// In template:
<?php if ($showTitle && $title): ?>
<!-- title markup -->
<?php endif; ?>$style = $block->getData('style') ?: 'default';
$styleClasses = match($style) {
'primary' => 'bg-blue-600 text-white',
'secondary' => 'bg-gray-200 text-gray-800',
default => 'bg-white text-gray-600'
};$block->createChildHtml()$children = $block->getData('children') ?: [];
// In template:
<?php foreach ($children as $index => $child): ?>
<?= /** @noEscape */ $block->createChildHtml($child, 'child-' . $index) ?>
<?php endforeach; ?>template: false"template": falsecontent$children = $block->getData('children') ?: [];
// In template - iterate and access child data directly:
<?php foreach ($children as $elementData): ?>
<?php
// Access fields directly on $elementData (NOT $elementData['content']['field'])
$image = $elementData['image'] ?? null;
$title = $elementData['title'] ?? '';
$description = $elementData['description'] ?? '';
// Each child has a 'uid' for editor attributes
$childUid = $elementData['uid'];
?>
<div <?= /** @noEscape */ $block->getEditorAttrs('', $childUid) ?>>
<?php if (!empty($image['src'])): ?>
<?php // For image rendering patterns, see the hyva-render-media-image skill ?>
<?= /** @noEscape */ $mediaViewModel->getResponsivePictureHtml(
[$block->getResponsiveImageData($image)],
['alt' => $image['alt'] ?? '', 'class' => 'w-full h-auto', 'loading' => 'lazy']
) ?>
<?php endif; ?>
<p <?= /** @noEscape */ $block->getEditorAttrs('title', $childUid) ?>>
<?= $escaper->escapeHtml($title) ?>
</p>
</div>
<?php endforeach; ?>template: false$elementData['field_name']$elementData['content']['field_name']uid$block->getEditorAttrs('field_name', $childUid)$block->getEditorAttrs('', $childUid)!empty($image['src'])$block->getResponsiveImageData($image)hyva-render-media-imagechildrenattributescomponents.jsonscripts/update_component_schema.phpreferences/component-schema.mdhyva-themes/commerce-module-cms{{CONTENT_FIELDS}}{{TEMPLATE_BODY}}getEditorAttrs()<script>alpine:initdefault_valuedefaultdefault_valuedefault"default_value": "My Title""default": "My Title"children"type": "children"children$block->getData('children')attributes"attributes": {"required": true}"required": trueattributes