| Duration | ~55 min in the lesson + ~25 min homework |
| Prerequisites | Checkpoint lesson-3.6. |
| Checkpoint | lesson-3.7 |
What you will have
The landing page has a quote-request form in the hero and in the final section. It checks input in the browser, shows clear error messages, and moves through idle, sending, success and error states. Submission is a temporary fake; the real endpoint is built in lesson 4.3.
Video
The video for this lesson is not recorded yet.
Prompts used in this lesson
Part 5 — Build it
Purpose: build the quote-request form, the conversion point of the site.
Interface only; the real endpoint comes in lesson 4.3.
Context: two reserved areas exist — id "quote" in the hero and id
"quote-bottom" in the final section. Labels, helper text, button text and
messages are in docs/content.md; put them in content/landing.ts with the rest
of the copy. Fields must match the future leads table: name, email, phone,
message. The privacy page will be at /privacy.
Done when:
- components/LeadForm.tsx is one reusable component used in both places,
replacing the reserved boxes;
- fields: name (required), email (required, checked for format), phone
(required; accept spaces, brackets, hyphens and a leading +; check only for
a plausible number of digits), message (optional, multi-line);
- each field has a visible label, the right input type and autocomplete
attribute so phones show the right keyboard and browsers can autofill, and
required fields are marked in text;
- validation runs when a field loses focus and again on submit; each error
appears next to its field, says what is wrong and how to fix it, is linked
to the field for screen readers, and is not shown by colour alone; on a
failed submit, focus moves to the first field with an error; typed values
are never cleared;
- four states: idle; sending (button disabled, text changes, cannot be
submitted twice); success (the form is replaced by a confirmation message
that screen readers announce); error (a message above the button with a
retry, the phone number from content/site.ts as an alternative, and all
typed values kept);
- submission goes through one function in lib/submit-lead.ts that takes
{ name, email, phone, message }. For now it is a fake: wait about one
second, then succeed; if the email is error@example.com, fail instead. Mark
the function clearly as temporary, to be replaced in lesson 4.3. It must
not send data anywhere or write it to the console.
- below the button: the "what happens next" line and a link to /privacy.
Constraints: only theme tokens and existing components (Button, Heading). No
form or validation library unless you first explain why it is needed and I
agree. The two form instances must not share ids. No analytics events and no
spam protection yet. Before reporting: build and lint; then in the browser
(1) submit empty, (2) submit with a bad email, (3) submit valid data, (4)
submit with error@example.com; take a screenshot of each result; run the axe
check from lesson 3.6 on the page with errors showing; report the results as
a table.Do along
Work on your own project and pause the video where told.
- Start from
lesson-3.6. - Pause after Part 2. Decide your fields. Keep the four names
name,email,phone,messageso later lessons fit; decide for your business whether phone is required. Write the decision and the reason indocs/brief.md. - Pause after Part 4. Write the form copy in
docs/content.md: labels, helper text, button text, the "what happens next" line, each error message, the success message and the failure message. Only promise a reply time you will keep. - Pause after Part 5. Run the build prompt from Part 5, changing the required/optional lines to match your decision.
- Pause after Part 6. Do the visitor test from Part 6 yourself: empty submit, bad email, odd phone formats, valid data, double click while sending,
error@example.com. - Repeat with the keyboard only, then on your phone at the live domain.
- Save the checkpoint with the commands under "Recap and next".
Check your work
- Submit the hero form empty. Expected: a message beside each required field saying what to enter; focus on the first field with an error; nothing typed is lost.
- Enter
anna@as the email and leave the field. Expected: a format message with an example; it disappears when corrected. - Enter a phone number with spaces and a
+. Expected: accepted. - Submit valid data. Expected: the button is disabled and shows the sending text for about a second, then the form is replaced by the success message.
- Reload, submit with
error@example.com. Expected: the error state with a retry, an alternative contact, and all values kept. - Repeat step 4 on the form in the final section. Expected: the same behaviour.
npm run buildandnpm run lint. Expected: both pass.
Common problems
- The browser's own grey bubble ("Please fill out this field") appears instead of your messages → the browser's built-in validation is taking over → "Turn off the browser's native validation bubbles and show our own messages; keep the fields' required and type attributes meaningful for assistive technology."
- A valid phone number is rejected → the check is too strict → give Claude the exact number that failed and ask it to accept common formats and count only digits.
- Claude built a real request to
/api/leads→ it went ahead of the plan; the endpoint does not exist and submissions fail → "Use the temporary fake in lib/submit-lead.ts exactly as specified; the endpoint is lesson 4.3."
Homework
About 25 minutes, after the lesson. No later lesson depends on it.
- Every message, aloud. Trigger each error message, the success message and the failure message, and read each one aloud. Rewrite any that sounds like a machine: change it in
docs/content.mdfirst, then ask Claude to sync. Deliverable: the revised messages. Commit without a tag. Done when: every error says what is wrong and how to fix it, in words you would say to a customer. - Watch one person. Ask someone to request a quote on the live site with their own phone, without help. The form is still the fake, so nothing is sent or saved. Deliverable: notes on where they hesitated and at which field. Done when: for each hesitation you have written "change the wording" or "leave it".
- Three other forms. Look at the quote or contact forms of three businesses like yours and count their fields. Deliverable: three lines added to your field decision in
docs/brief.md. Commit without a tag. Done when: you can say in one sentence why your form asks for exactly what it asks. Do not add or rename fields; later lessons rely on the four names.
Save your work
git add -A
git commit -m "Lesson 3.7: lead form UI with validation and temporary submission"
git tag lesson-3.7
git push
git push --tags