index.html — the contact form
Add a contact section to the page — a real, accessible form, built to validate itself and honest about where it stops. It goes inside <main>, below the gallery, and the nav gains a link to it.
A Contact link for the new section
<a href="#contact" class="nav-link">Contact</a>
The nav gains a Contact link. Like Home and Gallery, it's an in-page anchor: href="#contact" jumps to the element with id="contact" — the section you're adding just below. Every link in this nav points at a real section on the page; none is a placeholder. An anchor to an id is plain HTML, and it works with no router and no JavaScript.
The section, and an honest notice
<section id="contact" aria-label="Contact">
<div class="view-head">
<h2>Contact</h2>
<p class="muted">Questions, bugs, or just want to say hello?</p>
</div>
<div class="notice">
<strong>Note:</strong> this page has no server behind it. The browser checks
your entries before sending, but actually delivering a message needs a server
or JavaScript — which the client-side track covers.
</div>
The .notice says on the page what the theory said in words: the form validates, but it can't deliver. Telling the reader up front is better than letting them press Send and wonder what broke.
The form: a label and a real control per field
<form class="form-card">
<div class="field">
<label for="contact-name">Name <span class="required">*</span></label>
<input type="text" id="contact-name" name="name" maxlength="60" autocomplete="name" required>
</div>
<div class="field">
<label for="contact-email">Email <span class="required">*</span></label>
<input type="email" id="contact-email" name="email" autocomplete="email"
placeholder="you@example.com" required>
</div>
<div class="field">
<label for="contact-subject">Subject <span class="required">*</span></label>
<select id="contact-subject" name="subject" required>
<option value="">Choose a subject…</option>
<option value="question">Question</option>
<option value="bug">Bug report</option>
<option value="feedback">Feedback</option>
<option value="other">Other</option>
</select>
</div>
<div class="field">
<label for="contact-message">Message <span class="required">*</span></label>
<textarea id="contact-message" name="message" rows="6" maxlength="1000"
minlength="20" placeholder="Tell us what's on your mind…" required></textarea>
<p class="hint">Minimum 20 characters.</p>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Send message</button>
</div>
</form>
</section>
Read what each field is doing:
- Every
<label>has aforthat matches its field'sid— click the word, focus the field. - The email field is
type="email", so the browser both offers the @-keyboard and checks the shape on submit.autocomplete="email"lets a browser fill it from the user's saved details. - Subject is a
<select>with a deliberate first option that has an emptyvalue— combined withrequired, "Choose a subject…" doesn't count as an answer, so the browser makes the user pick a real one. - Message is a
<textarea>withminlength="20"— the browser refuses to submit until it's long enough, and the.hinttells the reader the rule before they hit it. - Every field carries
required; the red*in each label (.required) tells the reader which fields are mandatory before the browser does.
There is no novalidate and no JavaScript. The vanilla app has both — it turns the browser's validation off and does its own in js/forms.js. You're doing the opposite on purpose: with no JavaScript to lean on, the browser's built-in validation is exactly the tool you want, so you let it run.