
Hyva Cms Component
- 525 installs
- 78 repo stars
- Updated July 31, 2026
- hyva-themes/hyva-ai-tools
hyva-cms-component is a Hyvä AI skill that scaffolds Hyvä-compatible CMS components—blocks, widgets, and Tailwind/Alpine UI—for developers building Magento storefront merchandising content.
About
hyva-cms-component is a skill from hyva-themes/hyva-ai-tools that scaffolds CMS-ready components for Magento storefronts using Hyvä theme conventions. It produces blocks, widgets, and Tailwind CSS plus Alpine.js UI patterns so merchandising content ships faster without fighting legacy Luma layouts. Developers reach for hyva-cms-component when CMS pages need hero banners, promo grids, or content modules that must match Hyvä performance and accessibility standards. The skill encodes theme conventions—utility classes, Alpine interactions, and Magento block structure—so generated components drop into Page Builder or custom layouts with minimal rework.
- Hyvä theme conventions
- Magento CMS blocks
- Tailwind/Alpine scaffolding
- Merchant content widgets
- Storefront-ready markup
Hyva Cms Component by the numbers
- 525 all-time installs (skills.sh)
- +15 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #614 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hyva-themes/hyva-ai-tools --skill hyva-cms-componentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 525 |
|---|---|
| repo stars | ★ 78 |
| Last updated | July 31, 2026 |
| Repository | hyva-themes/hyva-ai-tools ↗ |
How do you scaffold Hyvä CMS components for Magento?
Scaffold Hyvä-compatible CMS components for Magento storefronts—blocks, widgets, and Tailwind/Alpine UI—so merchandising content ships fast with theme conventions.
Who is it for?
Magento developers on Hyvä themes who need CMS blocks and widgets that match Tailwind/Alpine conventions without hand-writing boilerplate.
Skip if: Skip hyva-cms-component when the store runs Luma-only themes, needs pure backend catalog APIs, or targets non-Magento platforms.
When should I use this skill?
The user asks to create Hyvä CMS components, Magento content blocks, or Tailwind/Alpine widgets for a Hyvä storefront.
What you get
Hyvä CMS block files, widget templates, Tailwind/Alpine UI markup, and Magento layout XML snippets.
- CMS block PHP templates
- Tailwind/Alpine widget markup
- layout XML snippets
Files
Hyvä CMS Component Creator
Overview
This skill guides the interactive creation of custom Hyvä CMS components for Magento 2. It supports creating components in new or existing modules, with field presets for common patterns and automatic setup:upgrade execution.
Command execution: For commands that need to run inside the development environment (e.g., bin/magento), use the hyva-exec-shell-cmd skill to detect the environment and determine the appropriate command wrapper.
Workflow
Step 1: Module Selection
If not already specified in the prompt, ask the user where to create the component:
Option A: New Module
Ask for both values (do not assume defaults without asking): 1. Vendor name (e.g., Acme) - Required, no default. Do not suggest a Vendor name, prompt for user input. 2. Module name - Suggest CmsComponents as default so user can press Enter to accept
Then use the hyva-create-module skill with:
dependencies:["Hyva_CmsBase"]composer_require:{"hyva-themes/commerce-module-cms": "^1.0"}
Option B: Existing Module
- Request the module path (can be in
app/code/,vendor/, or custom location) - Verify the module has
Hyva_CmsBaseas a dependency inetc/module.xml. If not present, add it. - Verify the module has
hyva-themes/commerce-module-cmsas a dependency incomposer.json. If not present, add it.
Step 2: Component Details
Gather component information:
1. Component name (snake_case, e.g., feature_card) 2. Label (display name in editor, e.g., "Feature Card") 3. Category (Layout, Elements, Media, Content, or Other) 4. Icon - Automatically select an appropriate icon:
Step 4a: Identify icons already in use Use the hyva-cms-components-dump skill to dump all current CMS components. Extract all icon values from the output to build a list of icons already in use by existing components.
Step 4b: Find available lucide icons List the SVG files in vendor/hyva-themes/magento2-theme-module/src/view/base/web/svg/lucide/ to get the full set of available icons.
Step 4c: Select the best fitting icon From the available lucide icons that are NOT already in use by another component:
- Choose the icon whose name best matches the purpose/meaning of the new component
- Consider semantic meaning (e.g.,
shopping-cart.svgfor cart-related,image.svgfor image-related,layout-grid.svgfor grid layouts) - Format the selected icon as
Hyva_Theme::svg/lucide/[icon-name].svg
If no suitable unused icon can be found, or if the lucide directory doesn't exist, leave the icon property unset.
Step 3: Field Selection
Offer field presets or custom field creation. See references/field-types.md "Field Presets" section for available presets (Basic Card, Image Card, CTA Block, Text Block, Feature Item, Testimonial, Accordion Item) or allow custom field definition.
For custom fields, iterate through each field asking: 1. Field name (snake_case) 2. Field type (see references/field-types.md) 3. Label 4. Default value (optional) 5. Required? (yes/no) - Note: This will be added as attributes.required, NOT as a direct field property 6. Any additional attributes (these go in the attributes object)
Step 4: Variant Support
Ask if the component needs template variants:
- If yes: Gather variant names and labels (e.g., default, compact, wide). See
references/variant-support.mdfor configuration details. - If no: Use single template
Step 5: Generate Files
Create the required files:
For New Modules
The hyva-create-module skill creates the base module structure. Then add the CMS-specific directories:
app/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)For Existing Modules
Create or update:
etc/hyva_cms/components.json(merge with existing if present)view/frontend/templates/elements/[component-name].phtml
Step 6: Run Setup
After creating files, run bin/magento setup:upgrade using the appropriate command wrapper detected by the hyva-exec-shell-cmd skill.
File Generation Details
components.json Structure
{
"[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"
]
}
}
}Valid Component Properties
IMPORTANT: Only specific properties are allowed at the component level. See references/component-schema.md for the complete schema reference.
Key properties: label (required), category, template, icon, children, require_parent, content, design, advanced, disabled, custom_properties.
Invalid properties that will cause schema errors:
hidden- Does not exist. Userequire_parent: truefor child-only components, ordisabled: true- Any property not listed in the schema reference
Children Configuration (CRITICAL)
IMPORTANT: children is a ROOT-LEVEL component property, NOT a field type within content, design, or advanced.
INCORRECT ❌:
{
"my_component": {
"content": {
"items": {
"type": "children",
"label": "Items"
}
}
}
}CORRECT ✅:
{
"my_component": {
"label": "My Component",
"children": {
"config": {
"accepts": ["child_component"],
"max_children": 10
}
},
"content": {
"title": {
"type": "text",
"label": "Title"
}
}
}
}In templates, access children via $block->getData('children'), NOT via a custom field name.
Field Validation (CRITICAL)
IMPORTANT: Field validation attributes like required must be placed in the attributes object, NOT as direct field properties.
INCORRECT ❌:
{
"title": {
"type": "text",
"label": "Title",
"required": true
}
}CORRECT ✅:
{
"title": {
"type": "text",
"label": "Title",
"attributes": {
"required": true
}
}
}Other validation attributes that go in attributes:
required(boolean)minlength/maxlength(string)min/max(for numbers)pattern(regex string)placeholder(string)comment(help text)- Custom data attributes for validation messages
Child-Only Components
For components that should only be used as children of other components (like list items), use require_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"]
}
}
}
}When template: false, the parent component renders the child data directly (NOT using $block->createChildHtml()). See "Rendering Children with template: false" below.
PHTML Template Structure
Every template must start with this header:
<?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 */Additional requirements: 1. $block->getEditorAttrs() on root element 2. $block->getEditorAttrs('field_name') on editable elements 3. Proper escaping with $escaper->escapeHtml() and $escaper->escapeHtmlAttr()
Template Patterns by Field Type
Text fields:
$title = $block->getData('title');
// In template:
<?php if ($title): ?>
<h2 <?= /** @noEscape */ $block->getEditorAttrs('title') ?>>
<?= $escaper->escapeHtml($title) ?>
</h2>
<?php endif; ?>Richtext/HTML fields:
$content = $block->getData('content');
// In template (no escaping for richtext):
<?php if ($content): ?>
<div <?= /** @noEscape */ $block->getEditorAttrs('content') ?>>
<?= /** @noEscape */ $content ?>
</div>
<?php endif; ?>Image fields:
Use the hyva-render-media-image skill for rendering images. It provides the complete API reference and code patterns for the \Hyva\Theme\ViewModel\Media view model.
Add these imports when rendering images:
// Additional imports for templates with images:
use Hyva\Theme\ViewModel\Media;
/** @var Media $mediaViewModel */
$mediaViewModel = $viewModels->require(Media::class);The data from $block->getData('image') can be passed directly to getResponsivePictureHtml():
$image = $block->getData('image');
// In template:
<?php if ($image): ?>
<?= /** @noEscape */ $mediaViewModel->getResponsivePictureHtml(
$image,
['class' => 'w-full h-auto', 'loading' => 'lazy']
) ?>
<?php endif; ?>For responsive images with separate desktop and mobile sources, see the hyva-render-media-image skill.
Link fields:
$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; ?>Boolean fields:
$showTitle = (bool) $block->getData('show_title');
// In template:
<?php if ($showTitle && $title): ?>
<!-- title markup -->
<?php endif; ?>Select fields:
$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'
};Children fields (with their own templates):
When child components have their own templates (default behavior), use $block->createChildHtml():
$children = $block->getData('children') ?: [];
// In template:
<?php foreach ($children as $index => $child): ?>
<?= /** @noEscape */ $block->createChildHtml($child, 'child-' . $index) ?>
<?php endforeach; ?>Rendering Children with `template: false`:
When child components have "template": false, the parent component renders them directly. Child data is flat - field values are directly on the child array, NOT nested under a content key.
$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; ?>Key points for `template: false` children:
- Child field data is flat: use
$elementData['field_name'], NOT$elementData['content']['field_name'] - Each child has a
uidproperty for editor attributes - Use
$block->getEditorAttrs('field_name', $childUid)to enable live editing of child fields - Use
$block->getEditorAttrs('', $childUid)on the child's root element - For images, check
!empty($image['src'])and use$block->getResponsiveImageData($image)to process the image data - For advanced image rendering patterns (responsive breakpoints, etc.), see the
hyva-render-media-imageskill
Resources
references/critical-patterns.md
READ THIS FIRST - Essential patterns and common mistakes including:
- Correct
childrenconfiguration (root-level vs field type) - Proper field validation with
attributes - Default value syntax
- Quick checklist before generating components
Read this file before generating any component to avoid common errors.
references/example-component.md
Complete end-to-end example showing a Feature Card component with:
- Full
components.jsondefinition - Matching PHTML template with all field types
- Supporting module files (registration.php, module.xml, composer.json)
- Directory structure overview
Read this file when you need a reference for how all the pieces fit together.
references/component-schema.md
Complete schema reference for component declarations, auto-generated from the Hyvä CMS JSON schema. Includes:
- Valid component-level properties
- Field declaration properties
- All field types
- Validation attributes
Read this file when validating component structure or when encountering schema validation errors.
Run scripts/update_component_schema.php after Hyvä CMS updates to regenerate.
references/field-types.md
Complete reference for all supported field types including:
- Field configuration syntax
- All available field types with examples
- Validation attributes
- Conditional visibility (show_if/hide_if)
- Field presets for common patterns
Read this file when generating field configurations.
references/variant-support.md
Guide for implementing template variants including:
- Directory structure for variant templates
- Variant field configuration in components.json
- Template implementation patterns
- Common variant patterns and best practices
Read this file when the user wants multiple layout options for a component.
references/troubleshooting.md
Solutions for common issues including:
- Schema validation errors
- Component not visible in editor
- Template not rendering
- Live editor not working
- Image display issues
- Fallbacks when dependent skills are unavailable
Read this file when encountering errors during component creation or testing.
scripts/update_component_schema.php
PHP script that reads the Hyvä CMS JSON schema files and regenerates references/component-schema.md. Run after upgrading hyva-themes/commerce-module-cms to ensure documentation stays current.
assets/templates/component/template.phtml.tpl
Base PHTML structure for CMS components.
Placeholders:
{{CONTENT_FIELDS}}- PHP variable declarations{{TEMPLATE_BODY}}- HTML template content
Important Guidelines
1. Always use `getEditorAttrs()` on the root element and on each editable field element 2. Never use `<script>` tags in templates - use Alpine.js via alpine:init event 3. Escape all user content with appropriate escaper methods 4. Use meaningful default values for better merchant experience 5. Include design/advanced sections via includes for consistency 6. Validate component names are snake_case with only lowercase letters, numbers, and underscores 7. CRITICAL: Use `default_value` key, NOT `default` - The correct JSON key for default values is default_value (with underscore), not default. Example: "default_value": "My Title" ✅, NOT "default": "My Title" ❌ 8. CRITICAL: `children` is a root-level property, NOT a field type - Never use "type": "children" in content/design/advanced. Declare children at component root level. Access via $block->getData('children') in templates. 9. CRITICAL: Validation goes in `attributes`, NOT as direct properties - Use "attributes": {"required": true} ✅, NOT "required": true ❌. All HTML5 validation attributes (required, minlength, maxlength, pattern, min, max) must be inside the attributes object.
<!-- Copyright © Hyvä Themes https://hyva.io. All rights reserved. Licensed under OSL 3.0 -->
<?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 */
// Content fields
{{CONTENT_FIELDS}}
// Design fields
$textAlign = $block->getData('text_align') ?: 'text-left';
$textColor = $block->getData('text_color') ?: '';
$backgroundColor = $block->getData('background_color') ?: 'transparent';
// Advanced fields
$classes = $block->getData('classes') ?: '';
$blockId = $block->getData('block_id') ?: '';
?>
<div <?= /** @noEscape */ $block->getEditorAttrs() ?>
<?php if ($blockId): ?>id="<?= $escaper->escapeHtmlAttr($blockId) ?>"<?php endif; ?>
class="<?= $escaper->escapeHtmlAttr($textAlign) ?> <?= $escaper->escapeHtmlAttr($classes) ?>"
style="background-color: <?= $escaper->escapeHtmlAttr($backgroundColor) ?>; <?php if ($textColor): ?>color: <?= $escaper->escapeHtmlAttr($textColor) ?>;<?php endif; ?>">
{{TEMPLATE_BODY}}
</div>
Hyvä CMS Component Schema Reference
Auto-generated file - Run scripts/update_component_schema.php to regenerate from the Hyvä CMS JSON schema.>
Schema source: vendor/hyva-themes/commerce-module-cms/src/liveview-editor/etc/hyva_cms/jsonschema/Component Declaration Properties
IMPORTANT: Only these properties are allowed at the component level. Using any other property will cause a schema validation error.
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | Yes* | - | The label of the component, this gives a user friendly name for the component in the page builder. If not set the name will be used instead. |
disabled | boolean | No | false | If true the component will be disabled and not available in the HYVA CMS Liveview Editor. |
category | string | No | "Other" | The category of the component, this will be used to group components in the UI of the page builder. |
template | string/boolean | No | Auto | Template path in format Vendor_Module::path/to/template.phtml, or false for child-only components. If not set, defaults to [Vendor]_[Module]::elements/[component-name].phtml |
icon | string | No | Default | Icon path in format Vendor_Module::path/to/icon.svg. Supports: jpg, jpeg, png, gif, webp, svg, avif. Defaults to Hyva_CmsLiveviewEditor::images/components/default.svg |
children | object/boolean | No | - | Set to true to enable children, or an object with config for specific configuration. |
content | object | No | - | Field definitions for the Content tab. |
design | object | No | - | Field definitions for the Design tab. |
advanced | object | No | - | Field definitions for the Advanced tab. |
custom_properties | array | No | - | Array for extending the component schema by developers. |
require_parent | boolean | No | false | If true the component can only be added as a child of a parent component which can accept it. |
*Required unless disabled: true is set.
Component Name Rules
Component names (the JSON object keys) must:
- Be alphanumeric with dashes and underscores only
- Be at least 3 characters long
- Match pattern:
^[a-zA-Z0-9-_]+$
Field Declaration Properties
Fields within content, design, and advanced sections support these properties:
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | The field type (see Field Types below) |
custom_type | string | No | Custom type identifier when type is custom_type |
label | string | Yes | The label for the field in the editor form |
default_value | any | No | The initial value when a new component is created |
show_if | object | No | Optionally defines conditional visibility rules. The field is displayed only when the referenced field's value matches any value in the specified array. |
hide_if | object | No | Optionally defines conditional visibility rules. The field is hidden when the referenced field's value matches any value in the specified array. |
attributes | object/string/number/integer/boolean/null/array | No | The attributes property sets HTML input attributes (placeholder, validation rules, etc.) for compatible field types. Any type allowed, below is expected format. |
options | any | No | For select fields: array of {value, label} or source model class |
config | object | No | Additional configuration (accepts, excludes, max_children for children) |
custom_properties | array | No | Array for extending the field schema |
Special Property: includes
The includes property in content, design, or advanced sections allows inheriting fields from another file:
{
"design": {
"includes": "Hyva_CmsBase::etc/hyva_cms/default_design.json"
}
}Or multiple files:
{
"design": {
"includes": [
"Hyva_CmsBase::etc/hyva_cms/default_design.json",
"Hyva_CmsBase::etc/hyva_cms/default_design_typography.json"
]
}
}Field Types
| Type | Description |
|---|---|
boolean | Toggle switch for true/false values |
color | Color picker input |
date | Date picker |
datetime | Date and time picker |
html | Raw HTML code input |
image | Image upload field |
link | URL/link configuration with target options |
multiselect | Multiple selection dropdown |
number | Numeric input |
range | Slider input for numeric values |
products | Product selector |
richtext | WYSIWYG rich text editor |
select | Dropdown selection |
searchable_select | Searchable dropdown for large option lists |
text | Single-line text input |
text-align | Text alignment selector |
textarea | Multi-line text input |
variant | Template variant selector |
widget | Magento widget selector |
custom_type | Custom field type (requires custom_type property) |
Validation Attributes
Common attributes for field validation:
| Attribute | Description |
|---|---|
required | Field must have a value (boolean) |
minlength | Minimum text length |
maxlength | Maximum text length |
min | Minimum numeric value |
max | Maximum numeric value |
step | Numeric step increment |
pattern | Regex pattern for validation |
placeholder | Placeholder text |
comment | Help text displayed below the field |
Custom Validation Messages
| Attribute | Description |
|---|---|
data-required-msg | Custom message for required validation |
data-min-msg | Custom message for minimum validation |
data-max-msg | Custom message for maximum validation |
data-pattern-msg | Custom message for pattern validation |
Invalid Properties
The following properties do NOT exist and will cause schema errors:
hidden- Userequire_parent: truefor child-only components, ordisabled: trueto hide from editor- Any property not listed in the Component Declaration Properties table above
Critical Patterns and Common Mistakes
This document highlights the most important patterns to follow and common mistakes to avoid when creating Hyvä CMS components.
1. Children Configuration
❌ INCORRECT - Children as a field type in content
{
"my_component": {
"label": "My Component",
"content": {
"items": {
"type": "children",
"label": "Items",
"config": {
"accepts": ["child_component"]
}
}
}
}
}Why this is wrong: children is NOT a field type. It cannot be used in content, design, or advanced sections.
✅ CORRECT - Children as root-level property
{
"my_component": {
"label": "My Component",
"children": {
"config": {
"accepts": ["child_component"],
"max_children": 10
}
},
"content": {
"title": {
"type": "text",
"label": "Title"
}
}
}
}Template access:
$children = $block->getData('children') ?: [];
foreach ($children as $child) {
echo $block->createChildHtml($child, 'child-' . $index);
}2. Field Validation Attributes
❌ INCORRECT - Validation as direct field property
{
"title": {
"type": "text",
"label": "Title",
"required": true,
"minlength": "3",
"maxlength": "100"
}
}Why this is wrong: Validation attributes must be inside the attributes object, not as direct properties on the field.
✅ CORRECT - Validation in attributes object
{
"title": {
"type": "text",
"label": "Title",
"attributes": {
"required": true,
"minlength": "3",
"maxlength": "100",
"pattern": "^[A-Za-z ]+$",
"placeholder": "Enter title"
}
}
}All validation attributes that go in `attributes`:
required(boolean)minlength/maxlength(string)min/max(for number/range fields)pattern(regex string)placeholder(string)comment(help text)- Custom data attributes (e.g.,
data-validation-message)
3. Default Values
❌ INCORRECT - Using "default" key
{
"title": {
"type": "text",
"label": "Title",
"default": "My Title"
}
}✅ CORRECT - Using "default_value" key
{
"title": {
"type": "text",
"label": "Title",
"default_value": "My Title"
}
}4. Root-Level Component Properties
These properties go at the component root level (same level as label, content, etc.):
label(required)categorytemplateiconchildren← Root-level property, NOT a field typerequire_parentcontentdesignadvanceddisabledvisiblecustom_properties
5. Field Properties vs Attributes
Field Properties (direct on field object):
type(required)label(required)default_valueattributes(object for validation/HTML attributes)config(object for field-specific configuration)options(array for select/multiselect)show_if/hide_if(conditional visibility)
Attributes (inside attributes object):
- HTML5 validation:
required,minlength,maxlength,min,max,pattern - Input attributes:
placeholder,rows,cols - Custom data attributes:
data-* - Help text:
comment
Quick Checklist
Before committing a component definition, verify:
- [ ]
childrenis at root level, NOT incontent/design/advanced - [ ] All validation uses
attributes.required, NOT directrequiredproperty - [ ] Default values use
default_value, NOTdefault - [ ] Component has
label(required) - [ ] Template path is correct or omitted for default path
- [ ] All field types are valid (no
"type": "children") - [ ] Template accesses children via
$block->getData('children') - [ ] Template uses
$block->getEditorAttrs()on root and editable elements - [ ] Proper escaping in template:
escapeHtml(),escapeHtmlAttr(),escapeUrl()
References
Complete Example: Feature Card Component
This example shows a complete Hyvä CMS component from definition to template.
Component Definition
File: app/code/Acme/CmsComponents/etc/hyva_cms/components.json
{
"feature_card": {
"label": "Feature Card",
"category": "Elements",
"template": "Acme_CmsComponents::elements/feature-card.phtml",
"content": {
"image": {
"type": "image",
"label": "Image"
},
"title": {
"type": "text",
"label": "Title",
"default_value": "Feature Title",
"attributes": {
"placeholder": "Enter feature title",
"required": true
}
},
"description": {
"type": "textarea",
"label": "Description",
"attributes": {
"placeholder": "Enter description",
"rows": "3"
}
},
"link": {
"type": "link",
"label": "Link"
},
"style": {
"type": "select",
"label": "Card Style",
"default_value": "default",
"options": [
{"value": "default", "label": "Default"},
{"value": "outlined", "label": "Outlined"},
{"value": "elevated", "label": "Elevated"}
]
}
},
"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"
]
}
}
}PHTML Template
File: app/code/Acme/CmsComponents/view/frontend/templates/elements/feature-card.phtml
<?php
declare(strict_types=1);
use Hyva\CmsLiveviewEditor\Block\Element;
use Hyva\Theme\Model\ViewModelRegistry;
use Hyva\Theme\ViewModel\Media;
use Magento\Framework\Escaper;
/** @var Element $block */
/** @var Escaper $escaper */
/** @var ViewModelRegistry $viewModels */
// Content fields
$image = $block->getData('image');
$title = $block->getData('title');
$description = $block->getData('description');
$link = $block->getData('link');
$linkData = $link ? $block->getLinkData($link) : null;
$style = $block->getData('style') ?: 'default';
// Design fields (from includes)
$textAlign = $block->getData('text_align') ?: 'text-left';
$textColor = $block->getData('text_color') ?: '';
$backgroundColor = $block->getData('background_color') ?: 'transparent';
// Advanced fields (from includes)
$classes = $block->getData('classes') ?: '';
$blockId = $block->getData('block_id') ?: '';
// Style classes based on selection
$styleClasses = match($style) {
'outlined' => 'border border-gray-300',
'elevated' => 'shadow-lg',
default => 'bg-white'
};
// Media view model for responsive images
/** @var Media $mediaViewModel */
$mediaViewModel = $viewModels->require(Media::class);
?>
<div <?= /** @noEscape */ $block->getEditorAttrs() ?>
<?php if ($blockId): ?>id="<?= $escaper->escapeHtmlAttr($blockId) ?>"<?php endif; ?>
class="p-6 rounded-lg <?= $escaper->escapeHtmlAttr($styleClasses) ?> <?= $escaper->escapeHtmlAttr($textAlign) ?> <?= $escaper->escapeHtmlAttr($classes) ?>"
style="background-color: <?= $escaper->escapeHtmlAttr($backgroundColor) ?>;<?php if ($textColor): ?> color: <?= $escaper->escapeHtmlAttr($textColor) ?>;<?php endif; ?>">
<?php if ($image): ?>
<div class="mb-4">
<?= /** @noEscape */ $mediaViewModel->getResponsivePictureHtml(
$image,
['class' => 'w-full h-auto rounded', 'loading' => 'lazy']
) ?>
</div>
<?php endif; ?>
<?php if ($title): ?>
<h3 <?= /** @noEscape */ $block->getEditorAttrs('title') ?>
class="text-xl font-semibold mb-2">
<?= $escaper->escapeHtml($title) ?>
</h3>
<?php endif; ?>
<?php if ($description): ?>
<p <?= /** @noEscape */ $block->getEditorAttrs('description') ?>
class="text-gray-600 mb-4">
<?= $escaper->escapeHtml($description) ?>
</p>
<?php endif; ?>
<?php if ($linkData): ?>
<a href="<?= $escaper->escapeUrl($linkData['url']) ?>"
<?php if (!empty($linkData['target'])): ?>target="<?= $escaper->escapeHtmlAttr($linkData['target']) ?>"<?php endif; ?>
class="inline-block text-blue-600 hover:text-blue-800 font-medium">
<?= $escaper->escapeHtml($linkData['title'] ?: 'Learn more') ?> →
</a>
<?php endif; ?>
</div>Module Files
For completeness, here are the supporting module files created by the hyva-create-module skill:
File: app/code/Acme/CmsComponents/registration.php
<?php
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Acme_CmsComponents',
__DIR__
);File: app/code/Acme/CmsComponents/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Acme_CmsComponents">
<sequence>
<module name="Hyva_CmsBase"/>
</sequence>
</module>
</config>File: app/code/Acme/CmsComponents/composer.json
{
"name": "acme/module-cms-components",
"description": "Custom Hyvä CMS components",
"type": "magento2-module",
"require": {
"magento/framework": "*",
"hyva-themes/commerce-module-cms": "^1.0"
},
"autoload": {
"files": [
"registration.php"
],
"psr-4": {
"Acme\\CmsComponents\\": ""
}
}
}Directory Structure
app/code/Acme/CmsComponents/
├── registration.php
├── composer.json
├── etc/
│ ├── module.xml
│ └── hyva_cms/
│ └── components.json
└── view/
└── frontend/
└── templates/
└── elements/
└── feature-card.phtmlKey Points Demonstrated
1. Editor attributes - $block->getEditorAttrs() on root, $block->getEditorAttrs('field_name') on editable fields 2. Field type handling - Text, textarea, image, link, and select fields 3. Conditional rendering - Only render elements when data exists 4. Proper escaping - escapeHtml() for text, escapeHtmlAttr() for attributes, escapeUrl() for URLs 5. Design/Advanced includes - Standard styling options via JSON includes 6. Responsive images - Using the Media view model for image rendering 7. Link data extraction - Using $block->getLinkData() for link fields
Hyvä CMS Component Field Types Reference
Complete reference for all field types supported in Hyvä CMS component declarations.
IMPORTANT: This document covers field types used within content, design, and advanced sections. The children property is NOT a field type - it is a root-level component property. See the main SKILL.md for children configuration.
Field Structure
Every field in content, design, or advanced sections follows this structure:
{
"field_name": {
"type": "field_type",
"label": "Display Label",
"default_value": "optional default",
"attributes": {},
"config": {},
"options": [],
"show_if": {},
"hide_if": {}
}
}Text Fields
text
Single-line text input.
{
"title": {
"type": "text",
"label": "Title",
"default_value": "Default Title",
"attributes": {
"placeholder": "Enter title",
"required": true,
"minlength": "3",
"maxlength": "100",
"pattern": "^[A-Za-z ]+$",
"data-required-msg": "Title is required",
"data-pattern-msg": "Only letters and spaces allowed"
}
}
}textarea
Multi-line text input.
{
"description": {
"type": "textarea",
"label": "Description",
"attributes": {
"placeholder": "Enter description",
"rows": "4"
}
}
}richtext
WYSIWYG rich text editor with formatting options.
{
"content": {
"type": "richtext",
"label": "Content",
"default_value": "<p>Default content</p>"
}
}html
Raw HTML code input (no formatting toolbar).
{
"custom_html": {
"type": "html",
"label": "Custom HTML"
}
}Numeric Fields
number
Numeric input with optional constraints.
{
"quantity": {
"type": "number",
"label": "Quantity",
"default_value": 1,
"attributes": {
"min": "0",
"max": "100",
"step": "1"
}
}
}range
Slider input for numeric values.
{
"gap_size": {
"type": "range",
"label": "Gap Size",
"default_value": 1,
"attributes": {
"min": "0",
"max": "10",
"step": "0.25"
},
"config": {
"value_suffix": "rem"
}
}
}Boolean
Toggle switch for true/false values.
{
"show_title": {
"type": "boolean",
"label": "Show Title",
"default_value": true
}
}Selection Fields
select
Dropdown selection with single choice.
{
"style": {
"type": "select",
"label": "Style",
"default_value": "primary",
"options": [
{"value": "primary", "label": "Primary"},
{"value": "secondary", "label": "Secondary"},
{"value": "default", "label": "Default"}
]
}
}select with source model
Use a PHP class for dynamic options.
{
"btn_style": {
"type": "select",
"label": "Button Style",
"default_value": "btn-primary",
"options": "Hyva\\CmsBase\\Model\\Config\\Source\\ButtonBackground"
}
}multiselect
Multiple selection dropdown.
{
"categories": {
"type": "multiselect",
"label": "Categories",
"options": [
{"value": "cat1", "label": "Category 1"},
{"value": "cat2", "label": "Category 2"},
{"value": "cat3", "label": "Category 3"}
]
}
}searchable_select
Searchable dropdown for large option lists.
{
"cms_block": {
"type": "searchable_select",
"label": "CMS Block",
"options": "Hyva\\CmsBase\\Model\\Config\\Source\\CmsBlocks"
}
}Media Fields
image
Image upload field.
{
"image": {
"type": "image",
"label": "Image",
"config": {
"allowed_extensions": ["jpg", "jpeg", "png", "gif", "webp", "svg"]
}
}
}color
Color picker input.
{
"background_color": {
"type": "color",
"label": "Background Color",
"default_value": "transparent",
"attributes": {
"data-pattern": "^#?[a-zA-Z0-9,().\\-\\s%]+$",
"data-pattern-msg": "Value must be a valid CSS background-color"
}
}
}Link Field
URL/link configuration with target options.
{
"link": {
"type": "link",
"label": "Link"
}
}Returns an array with: url, title, target, type
Date Fields
date
Date picker.
{
"start_date": {
"type": "date",
"label": "Start Date"
}
}datetime
Date and time picker.
{
"publish_at": {
"type": "datetime",
"label": "Publish At"
}
}Special Fields
text-align
Text alignment selector (left, center, right, justify).
{
"text_align": {
"type": "text-align",
"label": "Text Align",
"default_value": "text-left"
}
}variant
Template variant selector - allows different templates for the same component.
{
"variant": {
"type": "variant",
"label": "Variant",
"default_value": "default",
"options": [
{
"value": "default",
"label": "Default",
"template": "Vendor_Module::elements/component/default.phtml"
},
{
"value": "compact",
"label": "Compact",
"template": "Vendor_Module::elements/component/compact.phtml"
},
{
"value": "wide",
"label": "Wide",
"template": "Vendor_Module::elements/component/wide.phtml"
}
]
}
}widget
Magento widget selector.
{
"widget": {
"type": "widget",
"label": "Widget"
}
}products
Product selector (by SKU or category).
{
"products": {
"type": "products",
"label": "Products"
}
}children
IMPORTANT: children is NOT a field type. It is a root-level component property.
Do NOT use this in content, design, or advanced sections:
INCORRECT ❌:
{
"my_component": {
"content": {
"items": {
"type": "children",
"label": "Items"
}
}
}
}Instead, declare children at the component root level:
CORRECT ✅:
{
"my_component": {
"label": "My Component",
"children": {
"config": {
"accepts": ["button", "text"],
"excludes": ["slider"],
"max_children": 10
}
}
}
}In templates, access children data via $block->getData('children'). See the main SKILL.md for complete examples.
Conditional Visibility
Show or hide fields based on other field values.
show_if
{
"show_image": {
"type": "boolean",
"label": "Show Image",
"default_value": true
},
"image": {
"type": "image",
"label": "Image",
"show_if": {
"show_image": true
}
}
}hide_if
{
"use_custom_color": {
"type": "boolean",
"label": "Use Custom Color",
"default_value": false
},
"preset_color": {
"type": "select",
"label": "Preset Color",
"hide_if": {
"use_custom_color": true
},
"options": [
{"value": "red", "label": "Red"},
{"value": "blue", "label": "Blue"}
]
},
"custom_color": {
"type": "color",
"label": "Custom Color",
"show_if": {
"use_custom_color": true
}
}
}Validation Attributes
Common validation attributes for fields:
| Attribute | Description |
|---|---|
required | Field must have a value |
minlength | Minimum text length |
maxlength | Maximum text length |
min | Minimum numeric value |
max | Maximum numeric value |
step | Numeric step increment |
pattern | Regex pattern for validation |
placeholder | Placeholder text |
Custom Validation Messages
Use data- prefixed attributes for custom messages:
{
"attributes": {
"required": true,
"pattern": "^[A-Za-z]+$",
"data-required-msg": "This field is required",
"data-pattern-msg": "Only letters are allowed",
"data-min-msg": "Value is too small",
"data-max-msg": "Value is too large"
}
}Including Shared Configurations
Use includes to inherit common field definitions:
{
"my_component": {
"label": "My Component",
"content": {
"title": {
"type": "text",
"label": "Title"
}
},
"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"
]
}
}
}Available Include Files
From Hyva_CmsBase:
Hyva_CmsBase::etc/hyva_cms/default_design.json- Addsbackground_colorHyva_CmsBase::etc/hyva_cms/default_design_typography.json- Addstext_color,text_alignHyva_CmsBase::etc/hyva_cms/default_advanced.json- Addsclasses,block_id
Field Presets
Common field combinations for typical components:
Basic Card
{
"title": {"type": "text", "label": "Title"},
"description": {"type": "textarea", "label": "Description"},
"link": {"type": "link", "label": "Link"}
}Image Card
{
"image": {"type": "image", "label": "Image"},
"title": {"type": "text", "label": "Title"},
"description": {"type": "textarea", "label": "Description"},
"link": {"type": "link", "label": "Link"}
}CTA Block
{
"heading": {"type": "text", "label": "Heading"},
"subheading": {"type": "text", "label": "Subheading"},
"content": {"type": "richtext", "label": "Content"},
"button_text": {"type": "text", "label": "Button Text", "default_value": "Learn More"},
"link": {"type": "link", "label": "Button Link"},
"image": {"type": "image", "label": "Background Image"}
}Text Block
{
"title": {"type": "text", "label": "Title"},
"content": {"type": "richtext", "label": "Content"}
}Feature Item
{
"icon": {"type": "image", "label": "Icon"},
"title": {"type": "text", "label": "Title"},
"description": {"type": "textarea", "label": "Description"}
}Accordion Item
{
"title": {"type": "text", "label": "Title"},
"content": {"type": "richtext", "label": "Content"},
"expanded": {"type": "boolean", "label": "Initially Expanded", "default_value": false}
}Testimonial
{
"quote": {"type": "textarea", "label": "Quote"},
"author_name": {"type": "text", "label": "Author Name"},
"author_title": {"type": "text", "label": "Author Title"},
"author_image": {"type": "image", "label": "Author Image"}
}Troubleshooting Guide
Common issues when creating Hyvä CMS components and their solutions.
Schema Validation Errors
Symptom: Component doesn't appear in the editor, or console shows JSON schema errors.
Common causes: 1. Invalid property at component level - Check component-schema.md for allowed properties. Common mistake: using hidden instead of require_parent: true. 2. Invalid field type - Verify the type value exists in field-types.md. 3. Malformed JSON - Validate JSON syntax (missing commas, trailing commas, unquoted keys). 4. Component name too short - Names must be at least 3 characters.
Resolution: Run schema validation:
# Check JSON syntax
cat etc/hyva_cms/components.json | python3 -m json.toolComponent Not Visible in Editor
Symptom: Component created but doesn't appear in the Hyvä CMS editor.
Checklist: 1. Cache: Clear Magento cache after creating/modifying components:
bin/magento cache:clean config full_page2. Disabled flag: Ensure "disabled": true is not set on the component. 3. require_parent: If "require_parent": true, component only appears as child option in parent components. 4. Category: Component may be in a different category tab in the editor. 5. File location: Verify components.json is in etc/hyva_cms/ directory (not etc/).
Template Not Rendering
Symptom: Component appears in editor but shows blank or error on frontend.
Common causes: 1. Template path mismatch - Verify template value in components.json matches actual file location. 2. PHP syntax error - Check Magento/PHP logs for parse errors.
Resolution: 1. Check template path format: Vendor_Module::elements/component-name.phtml 2. Verify file exists at: view/frontend/templates/elements/component-name.phtml 3. Check Magento logs:
tail -f var/log/exception.log var/log/system.logLive Editor Not Working
Symptom: Changes in the live editor don't reflect, or fields aren't editable.
Common causes: 1. Missing `getEditorAttrs()` - Root element must have $block->getEditorAttrs(). 2. Missing field attributes - Editable fields need $block->getEditorAttrs('field_name').
Correct pattern:
<div <?= /** @noEscape */ $block->getEditorAttrs() ?>>
<h2 <?= /** @noEscape */ $block->getEditorAttrs('title') ?>>
<?= $escaper->escapeHtml($title) ?>
</h2>
</div>Image Not Displaying
Symptom: Image field has value but nothing renders.
Checklist: 1. Null check: Always check if image data exists before rendering:
<?php if ($image): ?>2. Media view model: Ensure Media class is imported and instantiated. 3. For children with `template: false`: Check !empty($image['src']) not just $image.
Dependent Skills Unavailable
If referenced skills (hyva-exec-shell-cmd, hyva-create-module, hyva-render-media-image) are not available:
- hyva-exec-shell-cmd: Manually determine environment. For Warden: prefix commands with
warden shell -c "...". For local: run commands directly. - hyva-create-module: Manually create module files (registration.php, composer.json, etc/module.xml) following the structure in
example-component.md. - hyva-render-media-image: Use basic
<img>tag or reference Hyvä Theme documentation for\Hyva\Theme\ViewModel\MediaAPI.
Variant Support Reference
Template variants allow a single component to offer multiple layout options. Users select a variant in the editor, and the corresponding template renders.
Directory Structure
When using variants, templates are organized in a subdirectory named after the component:
view/frontend/templates/elements/
└── feature-card/
├── default.phtml
├── compact.phtml
└── wide.phtmlComponent Configuration
Use the variant field type in the content section. Each option specifies its own template path.
{
"feature_card": {
"label": "Feature Card",
"category": "Elements",
"content": {
"variant": {
"type": "variant",
"label": "Layout",
"default_value": "default",
"options": [
{
"value": "default",
"label": "Default",
"template": "Vendor_Module::elements/feature-card/default.phtml"
},
{
"value": "compact",
"label": "Compact",
"template": "Vendor_Module::elements/feature-card/compact.phtml"
},
{
"value": "wide",
"label": "Wide",
"template": "Vendor_Module::elements/feature-card/wide.phtml"
}
]
},
"title": {"type": "text", "label": "Title"},
"description": {"type": "textarea", "label": "Description"}
},
"design": {
"includes": [
"Hyva_CmsBase::etc/hyva_cms/default_design.json"
]
},
"advanced": {
"includes": [
"Hyva_CmsBase::etc/hyva_cms/default_advanced.json"
]
}
}
}Important: When using the variant field type, omit the top-level template property. Each variant option specifies its own template.
Variant Field Properties
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Must be "variant" |
label | string | Yes | Display label in editor (e.g., "Layout", "Style") |
default_value | string | Yes | The value of the default variant option |
options | array | Yes | Array of variant options |
Variant Option Properties
| Property | Type | Required | Description |
|---|---|---|---|
value | string | Yes | Unique identifier for the variant |
label | string | Yes | Display label in the variant selector |
template | string | Yes | Full template path for this variant |
Template Implementation
Each variant template is a standard PHTML file with the same field access:
<?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 */
$title = $block->getData('title');
$description = $block->getData('description');
?>
<div <?= /** @noEscape */ $block->getEditorAttrs() ?> class="variant-specific-classes">
<?php if ($title): ?>
<h3 <?= /** @noEscape */ $block->getEditorAttrs('title') ?>>
<?= $escaper->escapeHtml($title) ?>
</h3>
<?php endif; ?>
<?php if ($description): ?>
<p <?= /** @noEscape */ $block->getEditorAttrs('description') ?>>
<?= $escaper->escapeHtml($description) ?>
</p>
<?php endif; ?>
</div>All variant templates share the same field data - only the HTML structure and styling differ.
Common Variant Patterns
Layout Variants
default,compact,wide,full-width- Use for different spacing/sizing options
Style Variants
light,dark,outlined,filled- Use for different visual treatments
Alignment Variants
left,center,right- Use when image/content positioning varies significantly
Best Practices
1. Consistent field access - All variants use the same $block->getData() calls 2. Meaningful names - Use descriptive variant values (compact not v2) 3. Default first - List the default variant as the first option 4. Shared styles - Extract common Tailwind classes to reduce duplication 5. Preview consideration - Ensure each variant is visually distinct in the editor
#!/usr/bin/env php
<?php
/**
* Update Component Schema Reference
*
* Reads the Hyvä CMS JSON schema files and generates the component-schema.md reference file.
* Run this script when Hyvä CMS is updated to ensure the skill documentation stays current.
*
* Usage: php scripts/update_component_schema.php
*/
declare(strict_types=1);
// Find project root (look for vendor directory)
$dir = __DIR__;
while ($dir !== '/' && !is_dir($dir . '/vendor')) {
$dir = dirname($dir);
}
if ($dir === '/') {
fwrite(STDERR, "Error: Could not find project root (no vendor directory found)\n");
exit(1);
}
$projectRoot = $dir;
$schemaDir = $projectRoot . '/vendor/hyva-themes/commerce-module-cms/src/liveview-editor/etc/hyva_cms/jsonschema';
if (!is_dir($schemaDir)) {
fwrite(STDERR, "Error: Schema directory not found: $schemaDir\n");
fwrite(STDERR, "Make sure hyva-themes/commerce-module-cms is installed.\n");
exit(1);
}
$componentDeclarationSchema = $schemaDir . '/component-declaration.json';
$fieldDeclarationSchema = $schemaDir . '/component-field-declaration.json';
if (!file_exists($componentDeclarationSchema)) {
fwrite(STDERR, "Error: Component declaration schema not found: $componentDeclarationSchema\n");
exit(1);
}
if (!file_exists($fieldDeclarationSchema)) {
fwrite(STDERR, "Error: Field declaration schema not found: $fieldDeclarationSchema\n");
exit(1);
}
$componentSchema = json_decode(file_get_contents($componentDeclarationSchema), true);
$fieldSchema = json_decode(file_get_contents($fieldDeclarationSchema), true);
if (json_last_error() !== JSON_ERROR_NONE) {
fwrite(STDERR, "Error: Failed to parse JSON schema\n");
exit(1);
}
// Extract component properties
$componentProps = $componentSchema['additionalProperties']['properties'] ?? [];
// Extract field properties
$fieldProps = $fieldSchema['additionalProperties']['properties'] ?? [];
// Extract field types
$fieldTypes = $fieldSchema['additionalProperties']['properties']['type']['enum'] ?? [];
// Extract validation attributes
$validationAttrs = $fieldSchema['additionalProperties']['properties']['attributes']['properties'] ?? [];
// Build markdown content
$md = <<<'HEADER'
# Hyvä CMS Component Schema Reference
> **Auto-generated file** - Run `scripts/update_component_schema.php` to regenerate from the Hyvä CMS JSON schema.
>
> Schema source: `vendor/hyva-themes/commerce-module-cms/src/liveview-editor/etc/hyva_cms/jsonschema/`
## Component Declaration Properties
**IMPORTANT:** Only these properties are allowed at the component level. Using any other property will cause a schema validation error.
| Property | Type | Required | Default | Description |
|----------|------|----------|---------|-------------|
HEADER;
// Add component properties
foreach ($componentProps as $name => $prop) {
$type = is_array($prop['type'] ?? null) ? implode('/', $prop['type']) : ($prop['type'] ?? 'any');
$required = $name === 'label' ? 'Yes*' : 'No';
$default = isset($prop['default']) ? (is_bool($prop['default']) ? ($prop['default'] ? '`true`' : '`false`') : "`\"{$prop['default']}\"`") : '-';
$comment = $prop['$comment'] ?? '';
// Special handling for some properties
if ($name === 'template') {
$default = 'Auto';
$comment = 'Template path in format `Vendor_Module::path/to/template.phtml`, or `false` for child-only components. If not set, defaults to `[Vendor]_[Module]::elements/[component-name].phtml`';
} elseif ($name === 'icon') {
$default = 'Default';
$comment = 'Icon path in format `Vendor_Module::path/to/icon.svg`. Supports: jpg, jpeg, png, gif, webp, svg, avif. Defaults to `Hyva_CmsLiveviewEditor::images/components/default.svg`';
} elseif ($name === 'children') {
$comment = 'Set to `true` to enable children, or an object with `config` for specific configuration.';
} elseif ($name === 'content') {
$comment = 'Field definitions for the Content tab.';
} elseif ($name === 'design') {
$comment = 'Field definitions for the Design tab.';
} elseif ($name === 'advanced') {
$comment = 'Field definitions for the Advanced tab.';
} elseif ($name === 'custom_properties') {
$comment = 'Array for extending the component schema by developers.';
}
$md .= "| `$name` | $type | $required | $default | $comment |\n";
}
$md .= <<<'SECTION'
*Required unless `disabled: true` is set.
### Component Name Rules
Component names (the JSON object keys) must:
- Be alphanumeric with dashes and underscores only
- Be at least 3 characters long
- Match pattern: `^[a-zA-Z0-9-_]+$`
## Field Declaration Properties
Fields within `content`, `design`, and `advanced` sections support these properties:
| Property | Type | Required | Description |
|----------|------|----------|-------------|
SECTION;
// Add field properties
$requiredFields = $fieldSchema['additionalProperties']['required'] ?? [];
foreach ($fieldProps as $name => $prop) {
$type = is_array($prop['type'] ?? null) ? implode('/', $prop['type']) : ($prop['type'] ?? 'any');
$required = in_array($name, $requiredFields) ? 'Yes' : 'No';
$comment = $prop['$comment'] ?? '';
// Simplify some descriptions
if ($name === 'type') {
$comment = 'The field type (see Field Types below)';
} elseif ($name === 'label') {
$comment = 'The label for the field in the editor form';
} elseif ($name === 'default_value') {
$comment = 'The initial value when a new component is created';
} elseif ($name === 'options') {
$comment = 'For select fields: array of {value, label} or source model class';
} elseif ($name === 'config') {
$comment = 'Additional configuration (accepts, excludes, max_children for children)';
} elseif ($name === 'custom_type') {
$comment = 'Custom type identifier when `type` is `custom_type`';
} elseif ($name === 'custom_properties') {
$comment = 'Array for extending the field schema';
}
$md .= "| `$name` | $type | $required | $comment |\n";
}
$md .= <<<'SECTION'
### Special Property: includes
The `includes` property in `content`, `design`, or `advanced` sections allows inheriting fields from another file:
```json
{
"design": {
"includes": "Hyva_CmsBase::etc/hyva_cms/default_design.json"
}
}
```
Or multiple files:
```json
{
"design": {
"includes": [
"Hyva_CmsBase::etc/hyva_cms/default_design.json",
"Hyva_CmsBase::etc/hyva_cms/default_design_typography.json"
]
}
}
```
## Field Types
| Type | Description |
|------|-------------|
SECTION;
// Add field types
$typeDescriptions = [
'boolean' => 'Toggle switch for true/false values',
'color' => 'Color picker input',
'date' => 'Date picker',
'datetime' => 'Date and time picker',
'html' => 'Raw HTML code input',
'image' => 'Image upload field',
'link' => 'URL/link configuration with target options',
'multiselect' => 'Multiple selection dropdown',
'number' => 'Numeric input',
'range' => 'Slider input for numeric values',
'products' => 'Product selector',
'richtext' => 'WYSIWYG rich text editor',
'select' => 'Dropdown selection',
'searchable_select' => 'Searchable dropdown for large option lists',
'text' => 'Single-line text input',
'text-align' => 'Text alignment selector',
'textarea' => 'Multi-line text input',
'variant' => 'Template variant selector',
'widget' => 'Magento widget selector',
'custom_type' => 'Custom field type (requires `custom_type` property)',
];
foreach ($fieldTypes as $type) {
$desc = $typeDescriptions[$type] ?? '';
$md .= "| `$type` | $desc |\n";
}
$md .= <<<'SECTION'
## Validation Attributes
Common attributes for field validation:
| Attribute | Description |
|-----------|-------------|
SECTION;
// Add validation attributes (non-data- prefixed ones)
$attrDescriptions = [
'required' => 'Field must have a value (boolean)',
'minlength' => 'Minimum text length',
'maxlength' => 'Maximum text length',
'min' => 'Minimum numeric value',
'max' => 'Maximum numeric value',
'step' => 'Numeric step increment',
'pattern' => 'Regex pattern for validation',
'placeholder' => 'Placeholder text',
'comment' => 'Help text displayed below the field',
];
foreach ($validationAttrs as $attr => $prop) {
if (strpos($attr, 'data-') !== 0) {
$desc = $attrDescriptions[$attr] ?? ($prop['$comment'] ?? '');
$md .= "| `$attr` | $desc |\n";
}
}
$md .= <<<'SECTION'
### Custom Validation Messages
| Attribute | Description |
|-----------|-------------|
SECTION;
// Add data- prefixed attributes
$dataAttrDescriptions = [
'data-required-msg' => 'Custom message for required validation',
'data-min-msg' => 'Custom message for minimum validation',
'data-max-msg' => 'Custom message for maximum validation',
'data-pattern-msg' => 'Custom message for pattern validation',
];
foreach ($validationAttrs as $attr => $prop) {
if (strpos($attr, 'data-') === 0 && strpos($attr, '-msg') !== false) {
$desc = $dataAttrDescriptions[$attr] ?? ($prop['$comment'] ?? '');
$md .= "| `$attr` | $desc |\n";
}
}
$md .= <<<'SECTION'
## Invalid Properties
The following properties do **NOT** exist and will cause schema errors:
- `hidden` - Use `require_parent: true` for child-only components, or `disabled: true` to hide from editor
- Any property not listed in the Component Declaration Properties table above
SECTION;
// Write the output file
$outputFile = dirname(__DIR__) . '/references/component-schema.md';
if (file_put_contents($outputFile, $md) === false) {
fwrite(STDERR, "Error: Failed to write output file: $outputFile\n");
exit(1);
}
echo "Successfully updated: $outputFile\n";
echo "Schema version based on: hyva-themes/commerce-module-cms\n";Related skills
FAQ
What does hyva-cms-component generate?
hyva-cms-component generates Hyvä-compatible CMS components for Magento storefronts, including blocks, widgets, and Tailwind/Alpine UI markup. Output follows Hyvä theme conventions so merchandising content integrates into Page Builder or custom layouts with minimal rework.
Does hyva-cms-component work with Luma themes?
hyva-cms-component targets Hyvä theme stacks with Tailwind CSS and Alpine.js patterns. Magento shops still on Luma-only themes should use Luma-specific frontend skills instead of this Hyvä CMS scaffolder.