Reusable UI building blocks. Write a plain HTML file, and it becomes a custom tag you can use anywhere.
Any HTML file in the components/ directory becomes a component. The filename determines the tag name with a what- prefix:
| File | Tag |
|---|---|
components/card.html | <what-card> |
components/user-card.html | <what-user-card> |
components/alert.html | <what-alert> |
A minimal component is just HTML:
<div class="greeting">
<p>Hello from a component!</p>
</div>
Use it in any page:
<what-greeting></what-greeting>
Declare props in a <what> block at the top of the component file. Props become template variables inside the component.
<what>
props = "title, subtitle"
defaults.title = "Untitled"
</what>
<div class="card">
<div class="card-header">
<h3>#title#</h3>
<if subtitle>
<p class="text-muted">#subtitle#</p>
</if>
</div>
<div class="card-body">
<slot/>
</div>
</div>
Props with defaults are optional. Props without defaults render as empty if not provided.
Pass prop values as attributes on the tag:
<what-card title="Team Members" subtitle="Active users">
<p>Card content goes here.</p>
</what-card>
You can use template variables in prop values:
<what-card title="Welcome, #user.full_name#">
<p>Your dashboard overview.</p>
</what-card>
The <slot/> tag marks where children content is injected. Everything between the opening and closing component tags replaces <slot/>.
<what>
props = "title"
</what>
<section class="p-6">
<h2>#title#</h2>
<div class="mt-4">
<slot/>
</div>
</section>
<what-section title="Features">
<ul>
<li>Fast server rendering</li>
<li>Zero JavaScript by default</li>
<li>File-based routing</li>
</ul>
</what-section>
If a component has no <slot/> tag, children content is discarded.
Pass structured data as JSON in a prop attribute. Use single quotes around the JSON value:
<what>
props = "features"
</what>
<ul class="space-y-2">
<loop data="#features#" as="feature">
<li>#feature.name# — #feature.description#</li>
</loop>
</ul>
<what-feature-list features='[
{"name": "Routing", "description": "File-based, automatic"},
{"name": "Auth", "description": "JWT cookie, role-based"},
{"name": "Forms", "description": "Validation and CRUD"}
]'></what-feature-list>
serde_json::Value, so standard JSON format is required.
Components can loop over their own props data. This is how you build dynamic, data-driven components:
<what>
props = "users"
</what>
<div class="table-container">
<table class="table table-hover">
<thead>
<tr><th>Name</th><th>Email</th></tr>
</thead>
<tbody>
<loop data="#users#" as="u">
<tr>
<td>#u.name#</td>
<td>#u.email#</td>
</tr>
</loop>
</tbody>
</table>
</div>
Loops also work with data fetched at the page level and passed down as a prop:
<!-- Page fetches data, component renders it -->
<what>
fetch.team = "local:users"
</what>
<what-user-table users="#team#"></what-user-table>
Components can use other components. They resolve in order, from the innermost outward:
<what>
props = "title"
</what>
<div class="grid grid-cols-2 gap-4">
<what-card title="#title#">
<what-badge variant="badge-success">Active</what-badge>
<slot/>
</what-card>
</div>
Components become powerful when you combine props, defaults, conditionals, and loops. Here are common patterns.
Declare several props with sensible defaults. Callers only override what they need:
<what>
props = "label, value, icon, color"
defaults.icon = ""
defaults.color = "indigo"
</what>
<div class="stat-card" style="border-left: 3px solid var(--color-#color#);">
<if icon>
<span class="stat-icon">#icon#</span>
</if>
<div class="stat-value">#value#</div>
<div class="stat-label">#label#</div>
</div>
<!-- Minimal usage — defaults apply -->
<what-stat-card label="Users" value="1,024"></what-stat-card>
<!-- Override color and icon -->
<what-stat-card label="Revenue" value="$42k" icon="$" color="green"></what-stat-card>
Use <if> inside component templates to vary output based on props:
<what>
props = "variant, href"
defaults.variant = "primary"
defaults.href = ""
</what>
<if href>
<a href="#href#" class="btn btn-#variant#"><slot/></a>
</if>
<unless href>
<button class="btn btn-#variant#"><slot/></button>
</unless>
Components can receive array data (as JSON or from a page fetch) and loop over it:
<what>
props = "tags"
</what>
<div class="tag-list">
<loop data="#tags#" as="tag">
<span class="badge">#tag#</span>
</loop>
</div>
<!-- Pass JSON array -->
<what-tag-list tags='["rust", "html", "css"]'></what-tag-list>
<!-- Or pass fetched data -->
<what-tag-list tags="#post.tags#"></what-tag-list>
Use one component inside another to build complex UIs from simple parts:
<what>
props = "title, author, tags"
defaults.tags = "[]"
</what>
<what-card title="#title#">
<p class="text-muted">By #author#</p>
<what-tag-list tags="#tags#"></what-tag-list>
<slot/>
</what-card>
wwwhat ships with two built-in components that you don't need to create yourself:
Generates pagination controls for loops with the paginate attribute:
<loop data="#posts#" as="post" paginate="10">
<div>#post.title#</div>
</loop>
<what-pagination base_url="/blog" current="#page#" total="#total_pages#">
</what-pagination>
Adds Cloudflare Turnstile CAPTCHA protection to forms:
<form w-action="create" w-store="contacts">
<input name="email" type="email" w-required>
<what-turnstile sitekey="your-site-key"></what-turnstile>
<button type="submit">Submit</button>
</form>