Code

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 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 a for that matches its field's id — 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 empty value — combined with required, "Choose a subject…" doesn't count as an answer, so the browser makes the user pick a real one.
  • Message is a <textarea> with minlength="20" — the browser refuses to submit until it's long enough, and the .hint tells 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.