<what>
title: "Step 8 — Forms & Validation"
active_step: 8
</what>
<h1 class="text-3xl font-bold mb-2" style="color: #111827; letter-spacing: -0.02em;">Step 8 — Forms & Validation</h1>
<p class="text-gray-500 text-sm mb-8">Server-side and client-side validation declared in HTML. No JavaScript required.</p>
<section class="mb-8">
<h2 class="text-lg font-semibold mb-3" style="color: #111827;">How validation works</h2>
<p class="text-gray-600 mb-4" style="line-height: 1.7;">
Add <code>w-validate</code> to any <code><form></code>. Annotate fields with validation attributes. wwwhat validates server-side on submit and also injects client-side validation without any JavaScript from you.
</p>
<pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code><form method="post" action="/w-action/contacts" w-validate>
<input name="email" type="email" w-required>
<input name="age" type="number" w-min="18" w-max="99">
<button type="submit">Save</button>
<button type="reset">Cancel</button>
</form>
<!-- CSRF token is auto-injected. No manual setup needed. --></code></pre>
</section>
<section class="mb-8">
<h2 class="text-lg font-semibold mb-3" style="color: #111827;">Live example — contact form</h2>
<if flash.success>
<div class="alert alert-success mb-4">#flash.success#</div>
</if>
<if flash.error>
<div class="alert alert-danger mb-4">#flash.error#</div>
</if>
<div class="card mb-6">
<div class="card-body">
<form method="post" action="/w-action/contacts" w-validate>
<div class="form-group">
<label class="form-label" for="contact-name">
Full name <span style="color: #ef4444;">*</span>
</label>
<input
type="text"
id="contact-name"
name="name"
class="form-control <if errors.name>border-red-400</if>"
placeholder="Jane Smith"
value="#old.name#"
w-required
w-min="2"
autocomplete="off"
data-1p-ignore
>
<if errors.name>
<div class="text-sm mt-1" style="color: #dc2626;">#errors.name#</div>
</if>
</div>
<div class="form-group">
<label class="form-label" for="contact-email">
Email address <span style="color: #ef4444;">*</span>
</label>
<input
type="email"
id="contact-email"
name="email"
class="form-control <if errors.email>border-red-400</if>"
placeholder="jane@example.com"
value="#old.email#"
w-required
autocomplete="off"
data-1p-ignore
>
<if errors.email>
<div class="text-sm mt-1" style="color: #dc2626;">#errors.email#</div>
</if>
</div>
<div class="form-group">
<label class="form-label" for="contact-message">
Message <span style="color: #ef4444;">*</span>
</label>
<textarea
id="contact-message"
name="message"
class="form-control <if errors.message>border-red-400</if>"
rows="4"
placeholder="Your message..."
w-required
w-min="10"
autocomplete="off"
data-1p-ignore
>#old.message#</textarea>
<if errors.message>
<div class="text-sm mt-1" style="color: #dc2626;">#errors.message#</div>
</if>
<div class="text-xs text-gray-400 mt-1">Minimum 10 characters</div>
</div>
<div class="flex gap-2">
<button type="submit" class="btn btn-primary">Send message</button>
<button type="reset" class="btn btn-outline">Cancel</button>
</div>
</form>
</div>
</div>
</section>
<section class="mb-8">
<h2 class="text-lg font-semibold mb-3" style="color: #111827;">Validation attributes</h2>
<div class="card mb-4">
<div class="card-body" style="padding: 0;">
<table style="width: 100%; border-collapse: collapse; font-size: 0.875rem;">
<thead>
<tr style="background: #f9fafb; border-bottom: 1px solid #e5e7eb;">
<th style="text-align: left; padding: 0.75rem 1rem; font-weight: 600; color: #374151;">Attribute</th>
<th style="text-align: left; padding: 0.75rem 1rem; font-weight: 600; color: #374151;">Effect</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom: 1px solid #f3f4f6;">
<td style="padding: 0.625rem 1rem;"><code>w-required</code></td>
<td style="padding: 0.625rem 1rem; color: #6b7280;">Field must not be empty</td>
</tr>
<tr style="border-bottom: 1px solid #f3f4f6;">
<td style="padding: 0.625rem 1rem;"><code>w-min="N"</code></td>
<td style="padding: 0.625rem 1rem; color: #6b7280;">Min length (text) or min value (number)</td>
</tr>
<tr style="border-bottom: 1px solid #f3f4f6;">
<td style="padding: 0.625rem 1rem;"><code>w-max="N"</code></td>
<td style="padding: 0.625rem 1rem; color: #6b7280;">Max length (text) or max value (number)</td>
</tr>
<tr>
<td style="padding: 0.625rem 1rem;"><code>type="email"</code></td>
<td style="padding: 0.625rem 1rem; color: #6b7280;">Valid email format enforced</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<section class="mb-8">
<h2 class="text-lg font-semibold mb-3" style="color: #111827;">Error display and value preservation</h2>
<p class="text-gray-600 mb-3" style="line-height: 1.7;">
On validation failure, <code>#errors.field#</code> contains the error message. <code>#old.field#</code> contains the previously submitted value so the user doesn't have to retype everything.
</p>
<pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code><input name="email" value="#old.email#" w-required>
<if errors.email>
<div class="error">#errors.email#</div>
</if></code></pre>
</section>
<section class="mb-8">
<h2 class="text-lg font-semibold mb-3" style="color: #111827;">Flash messages</h2>
<p class="text-gray-600 mb-3" style="line-height: 1.7;">
After a form submit, flash messages are set by the engine and consumed once on the next render. Use them to show success or error notices.
</p>
<pre class="bg-gray-50 p-4 rounded text-sm font-mono" style="border: 1px solid #e5e7eb; overflow-x: auto;"><code><if flash.success>
<div class="alert alert-success">#flash.success#</div>
</if>
<if flash.error>
<div class="alert alert-danger">#flash.error#</div>
</if></code></pre>
</section>
<div class="card" style="border-color: #bbf7d0; background: #f0fdf4;">
<div class="card-body">
<div class="text-sm font-semibold mb-2" style="color: #14532d;">What you learned</div>
<ul class="text-sm space-y-1" style="color: #166534; padding-left: 1.25rem; list-style: disc;">
<li><code>w-validate</code> on a form enables validation (both server + client-side)</li>
<li>Validation attrs: <code>w-required</code>, <code>w-min="N"</code>, <code>w-max="N"</code></li>
<li><code>#errors.fieldname#</code> contains the error message for that field</li>
<li><code>#old.fieldname#</code> repopulates the field after a failed submit</li>
<li><code>#flash.success#</code> and <code>#flash.error#</code> for one-time messages</li>
<li>CSRF tokens are auto-injected — every POST form is protected against forgery</li>
<li>Always include a <code><button type="reset"></code> to let users cancel</li>
</ul>
</div>
</div>