1990s Forms & Inputs — Collect user input with text fields, dropdowns, radio buttons, checkboxes, and textareas. HTML forms are the backbone of every interactive web app. Inspired by freeCodeCamp's Responsive Web Design certification.

01

HTML Form Structure

Complete

Why learn this?

Forms are how users talk to your app. Every sign-up, search, checkout, and survey is a form. Mastering input types, labels, fieldset, and legend is the foundation of all web interaction.

What you built

A developer survey with text inputs, email, number, dropdown, radio buttons, checkboxes, and a textarea. The form uses semantic HTML with proper label associations, fieldset grouping, and required attributes.

Key concepts

  • input type="text|email|number" — Different input types for different data
  • select / option — Dropdown menus for predefined choices
  • input type="radio" — Single-select from a group
  • input type="checkbox" — Multi-select options
  • textarea — Multi-line text input
  • fieldset / legend — Group related fields for accessibility
  • label for="id" — Associates text with an input (clicking label focuses the input)

Next up

Step 2 adds CSS to make the form visually organized — spacing, columns, focus states.

HTML form ▶ Run
<fieldset>
  <legend>About You</legend>

  <label for="name">Full Name</label>
  <input type="text" id="name"
    name="name" required>

  <label>Primary Role</label>
  <label><input type="radio"
    name="role" value="frontend">
    Front-End</label>
  <label><input type="radio"
    name="role" value="backend">
    Back-End</label>

  <label for="years">Years Coding</label>
  <input type="number" id="years"
    name="years" min="0" max="50">
</fieldset>

<button type="submit">
  Submit Survey
</button>
🏷️ Label trick: Wrapping a <input> inside a <label> automatically associates them — no for attribute needed. Great for radio/checkbox groups.
02

Form Styling

Complete

Why learn this?

Bare HTML forms look terrible — cramped, misaligned, and hard to read. CSS turns them into something users actually want to fill out.

What you built

Clean form layout with card-like fieldset sections, properly spaced labels, styled inputs with green focus rings (#00d4aa), inline radio/checkbox groups using flexbox, and a full-width submit button with hover and active states. The form is also responsive at 600px breakpoint.

Key concepts

  • :focus — Focus ring with box-shadow for keyboard navigation
  • accent-color — Custom color for radio/checkbox inputs
  • flex-wrap — Inline label groups that wrap on small screens
  • transition — Smooth hover/focus state transitions

Next up

Step 3 adds validation with :valid/:invalid CSS pseudo-classes and inline error messages.

Form CSS ▶ Run
fieldset {
  border: 1px solid #d0d5dd;
  border-radius: 10px;
  padding: 1.5em;
  background: #fff;
}

input, select, textarea {
  width: 100%;
  padding: 0.6em 0.8em;
  border: 1px solid #d0d5dd;
  border-radius: 6px;
  transition: border-color 0.2s;
}

input:focus {
  border-color: #00d4aa;
  box-shadow: 0 0 0 3px
    rgba(0, 212, 170, 0.15);
}

.inline-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5em 1.5em;
}

button {
  width: 100%;
  padding: 0.8em;
  background: #00d4aa;
  color: #fff;
  border: none;
  border-radius: 8px;
  font-weight: 700;
}
button:hover {
  background: #00b894;
}
🎨 Accent color: The accent-color CSS property tints radio buttons and checkboxes to match your brand. One line: input[type="radio"] { accent-color: #00d4aa; }
03

Validation & UX

Complete

Why learn this?

Forms that accept garbage data are worse than no form at all. HTML5 validation (required, pattern, type) catches errors before the data ever reaches your server.

What you built

Real-time validation styling: green borders on valid fields, red borders on invalid ones, inline error messages that appear only after the user interacts. Uses :valid/:invalid pseudo-classes with :not(:placeholder-shown) to avoid showing errors on empty untouched fields.

Key concepts

  • :valid / :invalid — CSS pseudo-classes for validation state
  • :not(:placeholder-shown) — Only show errors after user interaction
  • pattern attribute — Regex-based validation (e.g. pattern=".{2,}" for min length)
  • accent-color — Custom colored radio buttons and checkboxes

Next up

Step 4 adds form submission with FormData API, loading state, and a response summary.

Validation CSS ▶ Run
/* Valid field — green */
input:valid {
  border-color: #27ae60;
  background: #f0faf4;
}

/* Invalid field — red,
   but only after user types */
input:invalid:not(:placeholder-shown) {
  border-color: #e74c3c;
  background: #fef5f5;
}

/* Error message — hidden
   until field is invalid */
.error-msg {
  display: none;
}
input:invalid:not(:placeholder-shown)
  ~ .error-msg {
  display: block;
}
⚡ UX tip: :not(:placeholder-shown) prevents the red glow from showing on empty fields when the page first loads. Errors only appear after the user has typed something invalid.
04

Submission & Response

Complete

Why learn this?

A form that goes nowhere frustrates users. Handling form submission — collecting data, showing a loading state, displaying a response — is the bridge between UI and backend.

What you built

A complete submission flow: FormData API to collect all values, loading spinner on the submit button, simulated delay, styled success card showing a summary of submitted data, and a reset button to start over.

Key concepts

  • FormData API — Collect all form values including arrays from checkboxes
  • event.preventDefault() — Stop page reload on form submit
  • Loading state — Disabled button with CSS spinner animation
  • Success state — Replace form with a styled response card
Submission JS ▶ Run
form.addEventListener('submit', (e) => {
  e.preventDefault();

  const data = new FormData(form);
  const values = {};
  for (let [key, val] of data) {
    if (!values[key]) {
      values[key] = val;
    } else {
      if (!Array.isArray(values[key]))
        values[key] = [values[key]];
      values[key].push(val);
    }
  }

  // Show loading state
  submitBtn.disabled = true;
  submitBtn.innerHTML =
    '<span class="spinner"></span> Submitting...';

  setTimeout(() => {
    showResponse(values);
  }, 800);
});
📋 FormData tip: Checkbox groups with the same name attribute produce multiple entries in FormData. Loop through .entries() and collect duplicates into arrays — otherwise each check only captures the last value.
Model
DeepSeek V4 Flash
Total Tokens
~33K
Est. Cost
$0.01
Steps
4
Tokens measured from actual output files at ~0.28 tok/byte. Input estimated from turns multiplied by system + AGENTS.md context.

✅ Survey Form Complete

All 4 steps built. Ready to move to the next project:

03
Product LandingHero section, feature cards, testimonials, pricing table — a real marketing page