Components
A component is an HTML template that decides how an object of one type should be rendered visually.
Components are stored in /components/, one .html file each. The file name is the type it renders, optionally prefixed with its ancestors (e.g. Product.Faq.html matches a Faq nested inside a Product): the most specific match wins.
<!-- /components/Hero.html -->
<section>
<h1 x-bind="heading"></h1>
</section>
Anatomy
A component is plain HTML. CSS and JS may live inside the component, in a <style>/<script> tag. Shared CSS/JS is linked into a component (<link rel="stylesheet" href="/main.css">) relative to the /assets/ folder (see Assets).
In order to accept props in a component, you must use custom html directives.
x-bind="field"replaces the element's inner content with the field's value, or (if the field is a nested object) injects that object's own matched component in its place.x-bind:attr="field"replaces the element's attribute (likehreforsrc) with the field's value.x-if="condition"removes the element only if the condition evaluates false.x-for="field"repeats the element for each object in the field.
Each directive's value is an expression, not full JavaScript. It evaluates a safe subset of it (field access, comparisons, &&/||/??, ternaries, method calls like .includes()).
Directives run in a fixed order: x-for, then x-if, then x-bind, then x-bind:attr. An element is repeated first, and only then are its conditions checked and its values filled in. When x-bind and x-for are on the same element, x-bind wins and x-for is ignored.
A nested component only sees its own fields. $ refers to the top-level document being rendered (usually the page), so any component can read its values: x-bind="$.site.language.checkout".
Example
Design a structured nested object that follows a given schema (defined by _type).
{
"_type": "Features",
"heading": "Why Ninjas Choose Us",
"show": true,
"features": [
{ "_type": "Feature", "title": "Stealth Mode", "body": "Deploys are invisible until they're not." },
{ "_type": "Feature", "title": "Free Katana", "body": "Every plan ships with one (not really)." }
],
"links": [
{ "_type": "Link", "label": "Join the Dojo", "url": "/signup" }
]
}
Create a Features.html component and accept properties heading, show, features and links available in the Features schema. We can choose to embed components directly using x-for on links.
<!-- Features.html -->
<section>
<h2 x-bind="heading"></h2>
<div x-bind="features"></div>
<div x-if="show" x-for="links">
<a x-bind:href="url" x-bind="label"></a>
</div>
</section>
Now create the nested Feature component and accept title and body.
<!-- Feature.html (Features.Feature.html would work here too) -->
<div class="feature">
<h3 x-bind="title"></h3>
<p x-bind="body"></p>
</div>
<style>
.feature.closed p { display: none; }
</style>
<script>
/* click a title to show or hide its body */
for (const feature of document.querySelectorAll(".feature")) {
feature.querySelector("h3").addEventListener("click", () => feature.classList.toggle("closed"));
}
</script>
The build script turns the object into raw html output.
<link rel="stylesheet" href="/_styles/<hash>.css">
<section>
<h2>Why Ninjas Choose Us</h2>
<div>
<div class="feature">
<h3>Stealth Mode</h3>
<p>Deploys are invisible until they're not.</p>
</div>
<div class="feature">
<h3>Free Katana</h3>
<p>Every plan ships with one (not really).</p>
</div>
</div>
<div>
<a href="/signup">Join the Dojo</a>
</div>
</section>
<script type="module" src="/_scripts/<hash>.js"></script>