LearnAI ToolsCareerPractice BuildsPlayContact
HTMLBeginner~1.5 hours

Job Application Form

Create a complete form with text fields, dropdowns, checkboxes, and file upload.

FormsInput TypesAttributes

Overview

A job application form is the natural second HTML project because it forces you to move past the two or three input types most tutorials stop at. A real application needs plain text, an email address, a dropdown of positions, a number for years of experience, a set of checkboxes for skills that aren't mutually exclusive, radio buttons for an employment type that is mutually exclusive, and a file upload for a resume — five genuinely different input patterns, each suited to a different kind of answer.

This tutorial builds that form field by field, and leans on HTML's built-in validation attributes — `required`, `minlength`, `pattern`, `min`, `max` — so the browser rejects obviously invalid input before a single line of JavaScript is written.

What You'll Build
  • A `<fieldset>`/`<legend>` grouped Personal Details section with text, email, and phone inputs.
  • A Position dropdown (`<select>`) and a numeric Years of Experience field.
  • A group of Skills checkboxes, where any number can be checked.
  • A group of Employment Type radio buttons, where exactly one can be selected.
  • A resume file upload restricted to PDF/DOC files, plus a cover letter textarea.
  • A submit button that the browser won't activate until every required field passes validation.

Prerequisites

  • Basic HTML structure and how to nest elements.
  • The generic `<form>` element and the idea of name/value pairs being submitted.
  • Attributes in general — you will use `type`, `name`, `id`, `required`, `min`, `max`, `pattern`, and `accept`.
  • Labels and why `for`/`id` pairing matters (covered briefly here, in more depth in the Portfolio project).

Project Structure

The whole form lives in one file, `application.html`, inside a single `<form>` element. Related fields are grouped with `<fieldset>`, and each fieldset gets a `<legend>` as its visible, accessible title — this is the correct semantic pairing for "here is a named group of controls", and it is exactly what a paper application form does with section headers like "Personal Details" or "Employment Type". A `<div>` with a heading above it looks similar, but assistive tech does not automatically associate that heading with the fields below it the way it does with `<legend>`.

Every field uses the input type that matches its data — `email`, `tel`, `number`, `file` — rather than defaulting everything to `type="text"`. Correct types matter because browsers change their behavior based on them: `type="email"` validates the format, `type="number"` shows a numeric keypad on mobile and a spinner on desktop, and `type="file"` opens the operating system's native file picker.

Step 1: Scaffold the Page and Form Container

Start with the standard boilerplate and open the `<form>` itself. The `action`/`method` attributes describe where the data would be sent in a real deployment; here they are left as placeholders since this tutorial focuses on the markup.

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Application - Frontend Developer Role</title>
<meta name="description" content="Apply for the Frontend Developer role using this application form.">
</head>
<body>
<main>
<h1>Job Application: Frontend Developer</h1>
<p>Fields marked with * are required.</p>
<!-- method="post" is correct here: the submitted data (personal details, a file)
shouldn't be exposed in the URL the way method="get" would expose it -->
<form action="#" method="post" enctype="multipart/form-data">
<!-- enctype="multipart/form-data" is required whenever a form includes a
file input (added in Step 6) - without it, the file itself never gets sent -->
<!-- Fieldsets for each group of fields are added in the steps that follow -->
</form>
</main>
</body>
</html>

Step 2: Collect Personal Details

Personal details are grouped in a `<fieldset>`, with `<legend>` as its visible section title — the accessible equivalent of a bold heading over a group of paper form fields. Each field uses the input type suited to its data: `type="tel"` for a phone number hints a numeric keypad on mobile, and `pattern` constrains the phone number to a specific digit format without needing JavaScript.

<!-- Grouped with <fieldset>/<legend> rather than a <div> + heading: assistive tech
automatically announces the legend as this group's name whenever any field inside it gets focus -->
<fieldset>
<legend>Personal Details</legend>
<p>
<label for="fullName">Full Name *</label><br>
<input type="text" id="fullName" name="fullName" required minlength="2">
<!-- minlength blocks single-character "names" without any custom validation code -->
</p>
<p>
<label for="email">Email Address *</label><br>
<input type="email" id="email" name="email" required>
<!-- type="email" rejects obviously malformed addresses (e.g. missing @) before submit -->
</p>
<p>
<label for="phone">Phone Number *</label><br>
<input type="tel" id="phone" name="phone" required
pattern="[0-9]{10}" placeholder="10-digit number without spaces">
<!-- pattern enforces exactly 10 digits; placeholder tells the user the expected format -->
</p>
</fieldset>

Step 3: Add a Position Dropdown and Experience Field

Next comes the position dropdown and a numeric experience field. `<select>` is the right element whenever the user must choose exactly one option from a fixed, known list — it takes far less screen space than the equivalent radio button group would for five or more options.

<fieldset>
<legend>Role Details</legend>
<p>
<label for="position">Position Applying For *</label><br>
<!-- <select> is best for one choice out of many known options; every browser gives
it a native, keyboard-accessible dropdown for free -->
<select id="position" name="position" required>
<option value="">-- Select a position --</option>
<!-- Empty value keeps this placeholder option from silently counting as a real answer -->
<option value="frontend">Frontend Developer</option>
<option value="backend">Backend Developer</option>
<option value="fullstack">Full-Stack Developer</option>
<option value="designer">UI/UX Designer</option>
</select>
</p>
<p>
<label for="experience">Years of Experience *</label><br>
<input type="number" id="experience" name="experience" required min="0" max="50" step="1">
<!-- type="number" + min/max keeps the value inside a realistic, valid range -->
</p>
</fieldset>

Step 4: Add Skill Checkboxes

Skills are checkboxes, not radio buttons, because more than one can genuinely be true at once — a candidate can know both HTML and CSS. Each checkbox needs its own unique `id`, but they can all share the same `name` so the server receives them as one group of selected values.

<fieldset>
<legend>Skills (select all that apply)</legend>
<!-- Checkboxes, not radios: multiple skills can be true simultaneously, so the
"pick any number" input type is the correct one here -->
<p>
<input type="checkbox" id="skillHtml" name="skills" value="HTML">
<label for="skillHtml">HTML</label>
</p>
<p>
<input type="checkbox" id="skillCss" name="skills" value="CSS">
<label for="skillCss">CSS</label>
</p>
<p>
<input type="checkbox" id="skillJs" name="skills" value="JavaScript">
<label for="skillJs">JavaScript</label>
</p>
<p>
<input type="checkbox" id="skillReact" name="skills" value="React">
<label for="skillReact">React</label>
</p>
<!-- Every checkbox shares name="skills" so the server groups all checked values together -->
</fieldset>

Step 5: Add Employment-Type Radio Buttons

Employment type is the opposite case — a candidate is either applying full-time, part-time, or as a contractor, never more than one at once — so radio buttons are correct here, not checkboxes. Sharing the same `name="employmentType"` across all three is what makes them mutually exclusive: selecting one automatically deselects the others.

<fieldset>
<legend>Employment Type</legend>
<!-- Radio buttons, not checkboxes: exactly one employment type can apply, so only
one option should ever be selectable at a time -->
<p>
<input type="radio" id="typeFull" name="employmentType" value="full-time" checked>
<!-- checked marks a sensible default; the user can still pick a different option -->
<label for="typeFull">Full-Time</label>
</p>
<p>
<input type="radio" id="typePart" name="employmentType" value="part-time">
<label for="typePart">Part-Time</label>
</p>
<p>
<input type="radio" id="typeContract" name="employmentType" value="contract">
<label for="typeContract">Contract</label>
</p>
<!-- All three share name="employmentType" - that shared name is what makes them mutually exclusive -->
</fieldset>

Step 6: Add Resume Upload and Cover Letter

Resume upload needs `type="file"`, restricted with `accept` so the operating system's file picker filters to relevant formats by default. A cover letter is free-form and multi-line, so it gets a `<textarea>` instead of a single-line input.

<fieldset>
<legend>Resume &amp; Cover Letter</legend>
<p>
<label for="resume">Upload Resume (PDF or DOC) *</label><br>
<input type="file" id="resume" name="resume" accept=".pdf,.doc,.docx" required>
<!-- accept hints the OS file picker to show only matching file types, though users can still override it -->
</p>
<p>
<label for="coverLetter">Cover Letter</label><br>
<textarea id="coverLetter" name="coverLetter" rows="6"
placeholder="Tell us why you're a good fit for this role..."></textarea>
<!-- Not required: a cover letter is optional, unlike the resume above it -->
</p>
</fieldset>

Step 7: Add the Submit Button

Finally, the submit button. `type="submit"` is explicit rather than relying on `<button>`'s default, and it sits outside every `<fieldset>` since it applies to the whole form, not to one field group.

<!-- Sits outside every fieldset since it submits the whole form, not one group -->
<p>
<button type="submit">Submit Application</button>
</p>

Complete Code

Here is the full application form assembled in order, with a small stylesheet so every field group is easy to scan.

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Application - Frontend Developer Role</title>
<meta name="description" content="Apply for the Frontend Developer role using this application form.">
<style>
* { box-sizing: border-box; }
body { font-family: Arial, Helvetica, sans-serif; margin: 0; background: #f3f4f6; color: #222; line-height: 1.6; }
main { max-width: 640px; margin: 0 auto; padding: 30px 20px; }
h1 { color: #1d2940; }
fieldset { background: #fff; border: 1px solid #ddd; border-radius: 6px; padding: 16px 20px; margin-bottom: 20px; }
legend { font-weight: bold; padding: 0 8px; color: #2b3a55; }
label { display: block; font-weight: bold; margin-bottom: 4px; }
input[type="text"], input[type="email"], input[type="tel"], input[type="number"], select, textarea {
width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-family: inherit; font-size: 1rem;
}
input[type="checkbox"], input[type="radio"] { margin-right: 8px; }
button { background: #2b3a55; color: #fff; border: none; padding: 12px 24px; border-radius: 4px; cursor: pointer; font-size: 1rem; }
button:hover { background: #1d2940; }
</style>
</head>
<body>
<main>
<h1>Job Application: Frontend Developer</h1>
<p>Fields marked with * are required.</p>
<form action="#" method="post" enctype="multipart/form-data">
<!-- Named group of controls: <legend> is the accessible title for this fieldset -->
<fieldset>
<legend>Personal Details</legend>
<p>
<label for="fullName">Full Name *</label><br>
<input type="text" id="fullName" name="fullName" required minlength="2">
</p>
<p>
<label for="email">Email Address *</label><br>
<input type="email" id="email" name="email" required>
</p>
<p>
<label for="phone">Phone Number *</label><br>
<input type="tel" id="phone" name="phone" required
pattern="[0-9]{10}" placeholder="10-digit number without spaces">
</p>
</fieldset>
<fieldset>
<legend>Role Details</legend>
<p>
<label for="position">Position Applying For *</label><br>
<select id="position" name="position" required>
<option value="">-- Select a position --</option>
<option value="frontend">Frontend Developer</option>
<option value="backend">Backend Developer</option>
<option value="fullstack">Full-Stack Developer</option>
<option value="designer">UI/UX Designer</option>
</select>
</p>
<p>
<label for="experience">Years of Experience *</label><br>
<input type="number" id="experience" name="experience" required min="0" max="50" step="1">
</p>
</fieldset>
<fieldset>
<legend>Skills (select all that apply)</legend>
<p>
<input type="checkbox" id="skillHtml" name="skills" value="HTML">
<label for="skillHtml">HTML</label>
</p>
<p>
<input type="checkbox" id="skillCss" name="skills" value="CSS">
<label for="skillCss">CSS</label>
</p>
<p>
<input type="checkbox" id="skillJs" name="skills" value="JavaScript">
<label for="skillJs">JavaScript</label>
</p>
<p>
<input type="checkbox" id="skillReact" name="skills" value="React">
<label for="skillReact">React</label>
</p>
</fieldset>
<fieldset>
<legend>Employment Type</legend>
<p>
<input type="radio" id="typeFull" name="employmentType" value="full-time" checked>
<label for="typeFull">Full-Time</label>
</p>
<p>
<input type="radio" id="typePart" name="employmentType" value="part-time">
<label for="typePart">Part-Time</label>
</p>
<p>
<input type="radio" id="typeContract" name="employmentType" value="contract">
<label for="typeContract">Contract</label>
</p>
</fieldset>
<fieldset>
<legend>Resume &amp; Cover Letter</legend>
<p>
<label for="resume">Upload Resume (PDF or DOC) *</label><br>
<input type="file" id="resume" name="resume" accept=".pdf,.doc,.docx" required>
</p>
<p>
<label for="coverLetter">Cover Letter</label><br>
<textarea id="coverLetter" name="coverLetter" rows="6"
placeholder="Tell us why you're a good fit for this role..."></textarea>
</p>
</fieldset>
<p>
<button type="submit">Submit Application</button>
</p>
</form>
</main>
</body>
</html>
Live Preview

Sample Run

Open `application.html` in a browser, or use the live preview above, and try the following:

  • Click Submit Application with the Full Name field empty — the browser stops the submission and points at that field itself, because of `required`, with zero JavaScript.
  • Type a phone number with letters in it, like "abc1234567" — `pattern="[0-9]{10}"` rejects it on submit.
  • Check two or more Skills boxes at once, then select an Employment Type — the skills stay independently checked while picking a new employment type automatically unchecks the previous one.
  • Click "Upload Resume" — the operating system's native file picker opens, pre-filtered to PDF/DOC files because of the `accept` attribute.

Extend This Project

  • Add a "Portfolio URL" field using `type="url"` so the browser validates it looks like a real link.
  • Add a `<datalist>` of common job titles to the Position field so it behaves like an autocomplete text input instead of a fixed dropdown.
  • Split Full Name into separate First Name / Last Name inputs and validate each with its own `pattern`.
  • Add a required checkbox at the bottom ("I certify this information is accurate") before the submit button makes sense to click.
  • Style invalid fields with the `:invalid` CSS pseudo-class so validation errors are visually obvious before the user even tries to submit.

Summary

You built a complete, multi-field job application form using the input type that actually matches each piece of data — text, email, tel, number, checkbox, radio, and file — instead of defaulting everything to a single generic text box. Combined with `required`, `pattern`, `min`, `max`, and `accept`, the browser now enforces meaningful validation on its own, which is the foundation every JavaScript-powered form validation library builds on top of.