From prompt to CSP-compliant phtml template
Claude Code can sketch a new Hyva template in seconds, but only with the right project conventions as context does it produce genuinely usable code. This article shows how to scaffold a phtml template with a matching view model, generate Alpine.js components that stay CSP compliant, and verify AI-suggested Tailwind classes against the actual design system before they reach production.
Table of Contents
- 1. What building Hyva templates with AI assistance actually means
- 2. Project conventions as context for Claude Code
- 3. Practical example: scaffolding a new template
- 4. Generating Alpine.js components
- 5. Ensuring CSP compliance
- 6. Verifying Tailwind classes instead of inventing them
- 7. Common mistakes in AI-generated Hyva templates
- 8. A safe workflow: context files and prompting
- 9. Hyva templates compared: AI suggestion vs. project convention
- 10. Summary
- 11. FAQ
1. What building Hyva templates with AI assistance actually means
Building Hyva templates with AI assistance does not mean that Claude Code turns a short description into a production-ready phtml template. It means using a language model deliberately for the parts of the work that are clearly structured: creating the base file following an existing pattern, wiring up a view model through layout XML, and drafting a first Alpine.js component. The difference between a usable and an unusable result almost always comes down to the context Claude Code receives before generation, not to the model itself.
Without context, a language model falls back on generic knowledge about Tailwind, Alpine.js, and PHP, which often only partially matches the actual conventions of a specific Hyva project. This shows up most clearly in three places: the structure of a phtml template with view model access, the way Alpine.js components are wired up in a CSP-compliant manner, and the question of which Tailwind classes actually exist in the project. The following sections cover exactly these three areas in detail, each with a practical example from a running Magento 2 project.
2. Project conventions as context for Claude Code
A Hyva theme typically inherits from a parent theme such as hyva-themes/magento2-default-theme-csp and deliberately overrides individual templates. This inheritance structure is not visible to a language model without additional context. Asking Claude Code to simply generate a new product card template usually produces something technically functional but stylistically foreign, because neither the escaper calls the project actually uses, nor the view model naming convention, nor the Tailwind classes actually in use are known to the model.
The most reliable way to provide this context is to let Claude Code read two or three existing, similar templates from the theme directly before a new file is created. A CLAUDE.md file with clear rules about block iteration, CSP registration, and mandatory escaping, as already maintained in this project, helps further. This combination of concrete example files and explicit rules produces noticeably more consistent results than a purely textual description of the desired conventions.
3. Practical example: scaffolding a new template
A new template for a product review summary that should appear above the description on the product detail page serves as an example. The task given to Claude Code covered three parts: a view model interface for the review data, a matching layout XML fragment, and the actual phtml template. It was important that Claude Code first searched comparable templates in the Magento_Catalog/templates/product/view directory before generation, to adopt the actual structure of view model access and escaper calls.
The result was a template that did not replace the block child iteration but respected it, consistently used the escaper for output data, and matched the indentation and PHPDoc structure of the neighboring files. What mattered here was less the speed of generation and more the fact that a manual review needed almost no adjustments to conventions, because those conventions were already present in the prompt context.
#!/usr/bin/env bash
# scaffold-context.sh - collect project conventions before asking
# Claude Code to scaffold a new Hyva template
set -euo pipefail
THEME_DIR="app/design/frontend/Mironsoft/default"
TARGET_DIR="$THEME_DIR/Magento_Catalog/templates/product/view"
echo "[1/3] Listing existing sibling templates for style reference..."
find "$TARGET_DIR" -maxdepth 1 -name "*.phtml" -print
echo "[2/3] Extracting ViewModel access pattern from an existing template..."
grep -n "viewModels\|escapeHtml\|getChildNames" "$TARGET_DIR/description.phtml"
echo "[3/3] Writing the new template following the same structure..."
mkdir -p "$TARGET_DIR"
cat <<'PHTML' > "$TARGET_DIR/review-summary.phtml"
<?php
/** @var \Magento\Framework\View\Block\Template $block */
/** @var \Magento\Framework\Escaper $escaper */
/** @var \Mironsoft\ReviewSummary\ViewModel\ReviewSummary $viewModel */
$viewModel = $viewModels->require(\Mironsoft\ReviewSummary\ViewModel\ReviewSummary::class);
?>
<div class="mb-6" x-data="reviewSummary()">
<h3 class="text-lg font-semibold text-gray-900">
<?= $escaper->escapeHtml(__('Customer Ratings')) ?>
</h3>
<?php foreach ($block->getChildNames() as $childName): ?>
<?= $block->getChildHtml($childName) ?>
<?php endforeach; ?>
</div>
PHTML
echo "Template scaffolded, run bin/phpcs on the new file next."
4. Generating Alpine.js components
For the interactivity of the review summary, a collapsible area with a star rating filter, Claude Code was asked to produce an Alpine.js component following the pattern already used in the project. Hyva themes typically register components through Alpine.data() in a separate JavaScript file rather than as an inline object directly in the x-data attribute, as soon as the logic exceeds a few lines. This pattern keeps phtml templates readable and makes the component reusable.
Claude Code can implement this separation reliably when the prompt explicitly asks for it and an existing example, such as the mobile footer collapse component from footer.phtml, is provided as a reference. Without this hint, the model tends to write the entire logic as an inline object in x-data, which is unproblematic for simple components but quickly becomes hard to follow for more complex filters and makes testing harder.
// web/js/review-summary.js
// Alpine.js component for the collapsible review summary block.
// Registered via Alpine.data() so the phtml template only references
// the component name, keeping markup and logic separated.
document.addEventListener('alpine:init', () => {
Alpine.data('reviewSummary', () => ({
expanded: false,
activeStarFilter: null,
toggleExpanded() {
this.expanded = !this.expanded;
},
setStarFilter(stars) {
this.activeStarFilter = this.activeStarFilter === stars ? null : stars;
this.$dispatch('review-filter-changed', { stars: this.activeStarFilter });
},
isFilterActive(stars) {
return this.activeStarFilter === stars;
}
}));
});
5. Ensuring CSP compliance
Hyva themes with Content Security Policy enabled forbid inline event handlers such as onclick and require every inline script block to be explicitly whitelisted through the Hyva CSP component's registerInlineScript(). A model trained mostly on generic Alpine.js code occasionally suggests patterns that would be unproblematic in a standard web application but get blocked by the browser policy in a CSP-strict Hyva theme, for example an onclick attribute instead of x-on:click, or an inline script without an accompanying PHP call.
The most reliable protection against such mistakes is an automated check after every AI-assisted template generation that deliberately looks for forbidden patterns instead of relying solely on prompt discipline. A simple shell script that uses ripgrep to search for on-attributes and unregistered script blocks reliably catches the most common violations before the change enters the review process.
#!/usr/bin/env bash
# csp-audit.sh - detect common CSP violations in a freshly
# AI-generated Hyva template before it enters code review
set -euo pipefail
TEMPLATE="$1"
echo "[1/3] Checking for forbidden inline event handler attributes..."
if grep -nE 'on(click|load|change|submit)=' "$TEMPLATE"; then
echo "[FAIL] Inline event handler found, use x-on:event instead" >&2
exit 1
fi
echo "[2/3] Checking every inline <script> block is CSP-registered..."
script_count=$(grep -c '<script type="text/plain">' "$TEMPLATE" || true)
register_count=$(grep -c 'registerInlineScript' "$TEMPLATE" || true)
if [[ "$script_count" -gt "$register_count" ]]; then
echo "[FAIL] Inline script without matching registerInlineScript() call" >&2
exit 1
fi
echo "[3/3] Checking for eval or new Function usage..."
grep -nE 'eval\(|new Function\(' "$TEMPLATE" && exit 1
echo "[OK] No obvious CSP violations found in $TEMPLATE"
6. Verifying Tailwind classes instead of inventing them
A recurring problem in AI-generated Hyva templates is Tailwind classes that look syntactically correct but do not actually exist in the project, because they are neither defined in tailwind.config.js nor covered by the utility classes used elsewhere in the theme. A typical example is bg-primary-500, when the project actually uses a custom color palette with names like brand-orange or a numeric scale like orange-600. Because Tailwind's version 4 CSS-first approach only generates classes that actually appear in the source and match the configuration, an invented class does not raise an error, it simply results in missing styling.
This type of mistake is especially tricky because it only becomes visible during a visual review and is not caught by PHPStan or a PHP lint run. A small Python script that extracts all classes used in the generated template and diffs them against a list of classes actually used elsewhere in the project makes this check reproducible and integrates easily into the build step, right after the Tailwind build and before the static content deploy.
#!/usr/bin/env python3
# verify_tailwind_classes.py - flag Tailwind classes suggested by an
# AI model that do not exist anywhere else in the project design system
import re
import sys
from pathlib import Path
CLASS_ATTR = re.compile(r'class="([^"]+)"')
def extract_classes(file_path: Path) -> set[str]:
text = file_path.read_text(encoding="utf-8")
found: set[str] = set()
for match in CLASS_ATTR.finditer(text):
found.update(match.group(1).split())
return found
def load_known_classes(theme_dir: Path) -> set[str]:
known: set[str] = set()
for phtml_file in theme_dir.rglob("*.phtml"):
known.update(extract_classes(phtml_file))
return known
if __name__ == "__main__":
new_template = Path(sys.argv[1])
theme_root = Path("app/design/frontend/Mironsoft/default")
known_classes = load_known_classes(theme_root)
new_classes = extract_classes(new_template)
unknown = sorted(new_classes - known_classes)
if unknown:
print(f"Unverified Tailwind classes in {new_template}:")
for class_name in unknown:
print(f" - {class_name}")
sys.exit(1)
print(f"All classes in {new_template} match existing project usage.")
7. Common mistakes in AI-generated Hyva templates
Beyond invented Tailwind classes and CSP violations, a few other patterns show up repeatedly in AI-generated Hyva templates. The most common one: leftover jQuery selectors or Knockout bindings from generic Magento 2 training material, even though the project consistently relies on Alpine.js and loads neither jQuery nor UI components. A second pattern is replacing the getChildNames() iteration with hardcoded child block calls, which effectively defeats the Hyva block system and makes later layout extensions through XML harder.
A third, more subtle problem concerns missing escaping. Claude Code escapes output reliably once the context already shows that escapeHtml() or escapeHtmlAttr() are the project standard, but it occasionally forgets this for newly introduced variables, such as a review text loaded from an external API. A manual look at every unescaped output variable therefore remains mandatory, regardless of how reliable the model was elsewhere.
8. A safe workflow: context files and prompting
A repeatable workflow for AI-assisted Hyva templating does not start with the prompt for the new template, it starts with assembling the context: two or three structurally similar existing templates, the relevant sections of CLAUDE.md, an excerpt from tailwind.config.js, and, if available, the existing Alpine.js reference component. Only after that comes the actual task description with clearly stated requirements such as CSP compliance, using only existing design tokens, and preserving block iteration.
After generation, the same review rhythm applies as for any other AI-assisted change: read the diff in full, run the CSP audit script, check Tailwind classes against the design system, and visually inspect the template in the browser before it goes into the deploy process. This rhythm costs a few extra minutes for a single template, but it reliably prevents CSP violations or unstyled areas from only surfacing in production.
{
"task": "scaffold-hyva-template",
"target_directory": "app/design/frontend/Mironsoft/default/Magento_Catalog/templates/product/view",
"new_file": "review-summary.phtml",
"context_files": [
"Magento_Catalog/templates/product/view/description.phtml",
"Magento_Theme/templates/page/js/sticky-header.phtml",
"CLAUDE.md#coding-standards",
"web/tailwind/tailwind.config.js"
],
"requirements": {
"csp_compliant": true,
"no_jquery": true,
"no_knockout": true,
"preserve_block_iteration": true,
"escaper_required_for_all_output": true,
"tailwind_classes": "existing project classes only, no invented utility values"
},
"verification": {
"csp_audit": "bin/bash csp-audit.sh review-summary.phtml",
"tailwind_check": "python3 verify_tailwind_classes.py review-summary.phtml",
"static_analysis": "bin/analyse app/code/Mironsoft/ReviewSummary --level=5"
}
}
9. Hyva templates compared: AI suggestion vs. project convention
The following overview summarizes the most common differences between an unreviewed AI suggestion and the actual project convention. It is based on patterns observed repeatedly across several Hyva scaffolding tasks and serves as a checklist for the review after every AI-assisted template generation.
| Area | Unreviewed AI Suggestion | Project Convention | Why It Matters |
|---|---|---|---|
| Interactivity | jQuery selector or Knockout binding | Alpine.data() component | The theme deliberately loads neither jQuery nor UI components |
| Event handling | onclick="..." attribute | x-on:click with a registered function | Inline handlers are blocked by the CSP |
| Block structure | Hardcoded child block call | $block->getChildNames() iteration | Keeps layout extensibility through XML intact |
| Color classes | bg-primary-500 (does not exist) | bg-orange-600 (defined palette) | Invented classes produce no CSS output |
| Output | <?= $data['title'] ?> without escaper | $escaper->escapeHtml($title) | Prevents XSS from externally loaded data |
What stands out is that almost all the deviations in the table are not caused by a poorly written prompt, they are caused by missing context about the specific project. A model that has never seen an example file with getChildNames() cannot reliably apply that convention, no matter how precisely the textual description is worded.
Mironsoft
Hyva frontend development with structured AI usage
Hyva templates that actually fit your codebase?
We use Claude Code deliberately for scaffolding new Hyva templates and back every result with a CSP audit, Tailwind class verification, and a full diff review, so your frontend stays consistent and CSP compliant.
Template Scaffolding
Create new phtml templates that follow your existing conventions
CSP Audit
Check Alpine.js components for forbidden inline patterns
Design System Check
Verify Tailwind classes against your actual theme
10. Summary
Building Hyva templates with AI assistance works reliably for scaffolding new files, deriving view model structures from existing examples, and drafting first Alpine.js components, as long as Claude Code receives enough context about the actual project conventions before generation. Without that context, templates end up technically functional but stylistically and sometimes functionally off, particularly with regard to CSP compliance and the use of Tailwind classes that actually exist.
Three checks should be a fixed part of the review after every AI-assisted template generation: a CSP audit for forbidden inline patterns, a comparison of used Tailwind classes against the design system, and a manual look at escaping and block iteration. Anyone who consistently applies these three checks can put Claude Code to productive use for Hyva frontend work without accepting stylistic or security-relevant regressions.
Building Hyva Templates with AI Assistance - The Essentials at a Glance
Context Before Prompt
Provide existing templates, CLAUDE.md, and the Tailwind config before generation, do not just describe them.
CSP Audit
Check every generated template for inline handlers and missing registerInlineScript() calls.
Tailwind Verification
Compare suggested classes against classes actually used in the project instead of trusting them blindly.
Preserve the Block System
getChildNames() iteration and mandatory escaping must not be replaced by AI suggestions.