Courses Learn Blog Contact
Get Started →
Lesson 7

Forms and Tables In Depth

Learn to build working HTML forms with input types and labels, and create correctly structured tables to display data effectively.

Frontend Development Month 1: HTML, CSS & Tailwind 10 min read Free

Webbo3 Frontend Development · Month 1 · Lesson 7

Forms and Tables In Depth

Build a working HTML form that collects real user input, and a properly structured table that displays data clearly and accessibly.

Before we touch code, let's be clear about what a form actually is. A form is not "some inputs on a page"  it's a contract between your page and a server. When someone clicks submit, the browser packages up every named field inside that <form> and sends it somewhere. If you get the structure wrong, the data either doesn't arrive, arrives malformed, or arrives but the server can't tell which field is which. That's why we're going to build this slowly, testing after every addition, instead of writing the whole thing and hoping it works.

A table has a similar contract, but visual instead of server-side: it tells the browser and screen readers "this cell belongs to this row and this column." Get that structure wrong and sighted users see a slightly ugly grid, but screen reader users may not be able to make sense of the data at all. So we treat both topics with the same seriousness.

Part 1 — The Form Element

action and method what they actually do

action is the URL the browser sends the data to. method is how it's sent:

  • get — data is appended to the URL as a query string, e.g. ?username=chidi. Visible in the address bar, visible in browser history, visible in server logs. Fine for a search box. Never fine for a password.
  • post — data travels in the request body, not the URL. Not shown in the address bar. This is the default choice for login forms, signup forms, anything with personal data.

If you leave method off entirely, the browser silently falls back to "get." That's the single most common beginner bug in this whole lesson — a login form that "works" in testing but leaks the password into the URL, because nobody typed method="post".

required vs placeholder — don't confuse these

required stops submission until the field has a value — it's validation. placeholder is gray hint text that vanishes the moment the user types one character — it's a suggestion, not a label, and it disappears exactly when the user needs it most (while filling the field in, they can no longer see what it was asking for). Never rely on placeholder alone to tell someone what a field is for — pair it with a real <label> every time.

Worked example: a login form, built correctly

<form action="login.php" method="post">
  <label for="username">Username:</label>
  <input type="text" id="username" name="username" required placeholder="Enter username"><br><br>

  <label for="password">Password:</label>
  <input type="password" id="password" name="password" required placeholder="Enter password"><br><br>

  <button type="submit">Login</button>
</form>

Rendered result

🧪 Try it yourself #1

Copy the code above into a file, then click submit with both fields empty. The browser should refuse to submit and highlight the empty fields — that's the required attribute doing its job. Then delete required from the password field and try again. Notice the difference.

Part 2 — Input Types

Every input has a type attribute, and picking the right one is not cosmetic — it changes the keyboard shown on mobile, triggers built-in validation, and tells assistive technology what kind of data is expected.

Type Use it for What it does for you
email Email addresses @ key on mobile keyboard, browser checks for a valid email shape
password Passwords Characters masked with dots
number Age, quantity Numeric keypad on mobile, up/down arrows on desktop
tel Phone numbers Phone keypad, no letters
date Birthdays, deadlines Native calendar picker
checkbox Pick any number of options Multiple can be selected at once
radio Pick exactly one option Selecting one deselects the others (must share the same name)
file Uploads Opens the device's file picker

Worked example: a survey with checkboxes and radios

Notice something important below: the two checkboxes have different ids but the same name — that's what groups radio buttons into a single choice, and lets checkboxes submit as a list under one field name.

<label>Interests:</label><br>
<input type="checkbox" id="reading" name="interests" value="reading">
<label for="reading">Reading</label>
<input type="checkbox" id="hiking" name="interests" value="hiking">
<label for="hiking">Hiking</label><br><br>

<label>Favourite colour:</label><br>
<input type="radio" id="red" name="color" value="red">
<label for="red">Red</label>
<input type="radio" id="blue" name="color" value="blue">
<label for="blue">Blue</label>

Rendered result





🧪 Try it yourself #2

Click both checkboxes above — both stay checked. Now click "Red," then click "Blue" — Red switches off automatically. That's what sharing name="color" does. Now try renaming one radio's name in your own copy so they don't match, and click both — you'll see they no longer exclude each other. That's the bug you're watching for.

Part 3 — Textarea, Select, Button

<textarea> handles multi-line text (a message, a bio). <select> gives a dropdown built from <option value="..."> children — the value is what's actually submitted, the text between the tags is only what the user sees. <button> always needs an explicit type — inside a form, a button with no type defaults to type="submit", which surprises people who only wanted a decorative button.

<label for="country">Country:</label>
<select id="country" name="country">
  <option value="ng">Nigeria</option>
  <option value="gh">Ghana</option>
  <option value="za">South Africa</option>
</select><br><br>

<label for="notes">Notes:</label><br>
<textarea id="notes" name="notes" rows="4" cols="30"></textarea>

Rendered result

Part 4 — Tables: Structure and Correct Usage

A table is not a layout tool — it's for genuinely tabular data: rows and columns where each cell has a clear relationship to a header. Its skeleton is: <table><thead> (header row, using <th>) → <tbody> (data rows, using <td>) → optionally <tfoot> for a summary row.

Wrong vs right, side by side

Here is a table where <th> was left inside <tbody> instead of <thead>. Visually it can look almost identical to the correct version — that's exactly why this mistake slips through. The difference only shows up to screen readers and to any script trying to read the table's structure.

<!-- ✗ Wrong: th sitting inside tbody -->
<table>
  <tbody>
    <tr>
      <th>Name</th>
      <th>Age</th>
    </tr>
    <tr>
      <td>John Doe</td>
      <td>30</td>
    </tr>
  </tbody>
</table>
<!-- ✓ Right: th lives in its own thead -->
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Age</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>John Doe</td>
      <td>30</td>
    </tr>
  </tbody>
</table>

Rendered result (correct version)

Name Age
John Doe 30
Jane Doe 25

colspan and rowspan — merging cells

colspan="3" stretches one cell across three columns. rowspan="2" stretches one cell down two rows. Below, the 9–10 AM slot is shared by two subjects, so it spans two rows; lunch is the same across all three days, so it spans three columns.

<table>
  <thead>
    <tr>
      <th>Monday</th>
      <th>Tuesday</th>
      <th>Wednesday</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td rowspan="2">9:00–10:00 AM</td>
      <td>Math</td>
      <td>Science</td>
    </tr>
    <tr>
      <td>English</td>
      <td>History</td>
    </tr>
    <tr>
      <td colspan="3">12:00–1:00 PM (Lunch)</td>
    </tr>
  </tbody>
</table>

Rendered result

Monday Tuesday Wednesday
9:00–10:00 AM Math Science
English History
12:00–1:00 PM (Lunch)

🧪 Try it yourself #3

Copy the schedule table and add a Thursday column with its own Math/Science-style cells, keeping the lunch row spanning all four days now (colspan="4"). Count carefully — this is the exercise that trips people up most, because every added column also means updating any existing colspan numbers, or your table will visually break.

Part 5 — Accessible Forms: label + id, done properly

A <label> is not decoration — it's how a screen reader announces what a field is for, and it's how a sighted user can click the word "Email" and have focus jump into the email box. The pairing rule is strict: the label's for value must match the input's id value character-for-character. A typo here (for="Username" vs id="username") silently breaks the pairing — the browser won't warn you, it just quietly stops working.

<label for="email">Email:</label>
<input type="email" id="email" name="email">

Rendered result — click the word "Email" and watch the box get focus

Do this now — the full build

You've now practiced every piece separately. This final exercise makes you combine all of them into one real page — that combining step is where most of the actual learning happens, so don't skip it even if each piece felt easy on its own. Budget 40–50 minutes.

  1. Create contact.html.
  2. Build a <form method="post"> with five properly labelled fields: name (text), email (email), phone (tel), subject — make this a <select> with at least three options — and message (<textarea>).
  3. Every field gets a unique id, and every field gets a <label for="..."> matching it. Name and email should be required.
  4. Add <button type="submit">Send</button>.
  5. Below the form, build a three-column weekly study-schedule table using thead/tbody, with at least one colspan or rowspan used somewhere sensible (not just to prove you can).
  6. Test it: click submit empty (required fields should block it), click a label to confirm it focuses the right field, and open the page on your phone if you can — check the keyboard changes for the email/phone fields.
✅ Self-check — click to reveal after you finish
  • Five labelled fields, each id matched to its label's for
  • Name and email are required and actually block submission when empty
  • Every input has a name attribute
  • Table has a real thead with th, and tbody with td
  • colspan/rowspan numbers add up correctly across every row
  • No unclosed tr or td tags

Common mistakes, and how to catch them yourself

  • Missing name attribute. The field looks fine on screen but nothing arrives server-side. Catch it by scanning every <input>, <select>, and <textarea> tag and confirming each has one.
  • Unclosed tr/td. One missing closing tag can silently reshape every row below it. Catch it by indenting consistently and checking that indentation returns to the same level after each closing tag.
  • Wrong input type. Using text for email or phone loses free validation and the right mobile keyboard. Catch it by asking "what shape of data is this?" before typing the tag.
  • label/for and id mismatch. Nothing breaks visually, so this is the easiest to miss. Catch it by clicking each label in the rendered page — if focus doesn't jump to the field, the pairing is wrong.
  • th used outside thead. Looks identical to a correct table visually. Catch it by reading your table's skeleton top to bottom before filling in content: table → thead → tr → th, then tbody → tr → td.

Questions students ask

  1. Why bother with method="post" if get "works" too? Get exposes data in the URL — fine for a public search, unacceptable for anything private. Post keeps it in the request body.
  2. Can I skip labels and just use placeholder text? No — placeholder disappears once typing starts, and screen readers don't treat it as a substitute for a label. Always use both.
  3. Do I need thead/tbody for a tiny two-row table? Yes. Table size doesn't change the accessibility contract — a two-row table with th outside thead is just as broken for a screen reader as a fifty-row one.
  4. What's the difference between colspan and rowspan again? colspan merges across columns (horizontally); rowspan merges down rows (vertically). If you mix them up, picture the cell physically stretching in that direction.

Quick recap: action/method · required vs placeholder · input types · textarea/select/button · table structure (thead/tbody/th/td) · colspan/rowspan · label+id pairing · full contact form + schedule table build

Next lesson: Your Online Presence: LinkedIn and GitHub.

Learn this with a real instructor

Free lessons take you a long way on your own. The full programmes add live classes, assignments, Skill Points and a certificate — built for African students.