Collect structured feedback
A survey mixes rating scales, choices, and open comments. Group related controls with <fieldset> and a <legend> so the question reads clearly to everyone.
HTML
<form>
<fieldset>
<legend>How likely are you to recommend us? (1–5)</legend>
<div class="scale">
<label>1 <input type="radio" name="nps" value="1"></label>
<label>2 <input type="radio" name="nps" value="2"></label>
<label>3 <input type="radio" name="nps" value="3"></label>
<label>4 <input type="radio" name="nps" value="4" checked></label>
<label>5 <input type="radio" name="nps" value="5"></label>
</div>
</fieldset>
<label for="hear">How did you hear about us?</label>
<select id="hear">
<option>Search engine</option>
<option>Social media</option>
<option>A friend</option>
<option>Other</option>
</select>
<label for="comments">Anything else?</label>
<textarea id="comments" rows="3" placeholder="Your comments…"></textarea>
<button type="submit">Submit survey</button>
</form>
CSS
fieldset { border: none; padding: 0; margin-bottom: 16px; }
legend { font-weight: 600; margin-bottom: 6px; }
.scale { display: flex; gap: 16px; } /* rating row */
select, textarea {
width: 100%; padding: 10px 12px;
border: 1px solid #cbd5e1; border-radius: 8px;
}
Let people search your site
Use type="search" for the input and wrap it in a <form role="search">. A flex row keeps the field and button on one line.
HTML
<form role="search" class="search"> <input type="search" aria-label="Search" placeholder="Search the site…"> <button type="submit">Search</button> </form>
CSS
.search { display: flex; gap: 8px; }
.search input { flex: 1; } /* input grows, button stays */
.search input[type="search"] {
padding: 10px 12px;
border: 1px solid #cbd5e1; border-radius: 8px;
}
Pick a date — natively
No library needed: <input type="date"> gives you the browser's built-in calendar. Add min / max to limit the range.
HTML
<label for="dob">Choose a date</label>
<input type="date" id="dob"
min="2020-01-01" max="2030-12-31">
CSS
input[type="date"] {
width: 100%;
padding: 10px 12px;
border: 1px solid #cbd5e1;
border-radius: 8px;
font: inherit; /* match your form's font */
}
input[type="date"]:focus { outline: 2px solid #38bdf8; }
Pick a time — natively
Its sibling, <input type="time">, provides a time selector. Use step to control the minute increments (e.g., 900 = 15-minute steps).
HTML
<label for="start">Start time</label> <input type="time" id="start" value="09:00"> <label for="end">End time</label> <input type="time" id="end" value="17:00" step="900"> <!-- 15-min steps -->
CSS
input[type="time"] {
width: 100%;
padding: 10px 12px;
border: 1px solid #cbd5e1;
border-radius: 8px;
font: inherit;
}
.row2 { display: flex; gap: 14px; } /* two pickers side by side */
The single-line field — and its states
Every text field needs a visible <label>. Beyond the default, show users helper text, error, and disabled states so the form communicates clearly.
HTML
<label for="name">Full name</label> <input id="name" type="text" placeholder="Jordan Parker"> <label for="user">Username</label> <input id="user" type="text" value="jp_designer"> <p class="help">3–20 characters, letters and numbers.</p> <label for="email">Email</label> <input id="email" type="email" class="err" value="jordan@"> <!-- error state --> <p class="err-msg">Enter a valid email address.</p> <label for="acct">Account ID</label> <input id="acct" type="text" value="A-10293" disabled> <!-- disabled --> <p class="help">Assigned automatically — can't be changed.</p>
CSS
input { width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; }
input:focus { outline: 2px solid #38bdf8; border-color: #38bdf8; }
input.err { border-color: #ef4444; } /* invalid */
input:disabled { background: #f1f5f9; color: #9ca3af; } /* disabled */
.help { font-size: .78rem; color: #9aa3b2; }
.err-msg { font-size: .78rem; color: #ef4444; } /* error text */
type (email, tel, url, number) — mobile keyboards adapt and the browser validates for free.Multi-line text
A <textarea> is for longer input — comments, messages, bios. Let users drag to resize vertically, and hint at the limit.
HTML
<label for="msg">Message</label> <textarea id="msg" rows="4" maxlength="500" placeholder="Write your message…"></textarea> <p class="help">Up to 500 characters. Drag the corner to resize.</p>
CSS
textarea {
width: 100%; padding: 10px 12px;
border: 1px solid #cbd5e1; border-radius: 8px;
min-height: 90px;
resize: vertical; /* let users grow it, not sideways */
}
.help { font-size: .78rem; color: #9aa3b2; }
Choose from a list
A native <select> is accessible and works on every device. Use <optgroup> to group options, and multiple when users can pick several.
HTML
<label for="country">Country</label>
<select id="country">
<optgroup label="North America">
<option>Canada</option>
<option>Mexico</option>
<option>United States</option>
</optgroup>
<optgroup label="Europe">
<option>France</option>
<option>Germany</option>
<option>Spain</option>
</optgroup>
</select>
<label for="skills">Skills (pick a few)</label>
<select id="skills" multiple size="4"> <!-- choose several -->
<option selected>HTML</option>
<option selected>CSS</option>
<option>JavaScript</option>
<option>Design</option>
</select>
<p class="help">Hold Ctrl / Cmd to select more than one.</p>
CSS
select {
width: 100%; padding: 10px 12px;
border: 1px solid #cbd5e1; border-radius: 8px;
font: inherit; background: #fff;
}
Yes/no and multi-select
A checkbox is an independent on/off choice. Use one alone (e.g. “I agree”) or a group when several can be selected at once. Wrap each in a <label> so the text is clickable too.
HTML
<label><input type="checkbox" checked> I agree to the terms</label> <fieldset> <legend>Pizza toppings</legend> <label><input type="checkbox" name="top" value="cheese" checked> Cheese</label> <label><input type="checkbox" name="top" value="mushrooms"> Mushrooms</label> <label><input type="checkbox" name="top" value="peppers" checked> Peppers</label> </fieldset>
CSS
label { display: flex; align-items: center; gap: 9px; } /* clickable row */
input[type="checkbox"] { accent-color: #38bdf8; } /* brand color */
Pick exactly one
Radios in the same name are mutually exclusive — choosing one clears the others. Stack them, or lay them inline for short option sets.
HTML
<label><input type="radio" name="plan" value="free" checked> Free</label> <label><input type="radio" name="plan" value="pro"> Pro — $9/mo</label> <label><input type="radio" name="plan" value="team"> Team — $29/mo</label> <!-- same name = only one can be chosen --> <div class="inline"> <label><input type="radio" name="ship" checked> Home</label> <label><input type="radio" name="ship"> Work</label> <label><input type="radio" name="ship"> Pickup</label> </div>
CSS
input[type="radio"] { accent-color: #38bdf8; }
.inline { display: flex; gap: 18px; } /* lay options in a row */
An instant on/off switch
A toggle is a checkbox styled as a sliding switch — best for settings that apply immediately. Built from a hidden checkbox plus a styled track.
HTML
<label class="toggle"> <input type="checkbox" checked> <span class="track"></span> Email notifications </label> <label class="toggle"> <input type="checkbox"> <span class="track"></span> Public profile </label>
CSS
.toggle input { display: none; }
.track { width: 44px; height: 26px; border-radius: 999px; background: #cbd5e1; position: relative; }
.track::after { content: ""; position: absolute; top: 3px; left: 3px;
width: 20px; height: 20px; border-radius: 50%; background: #fff; transition: left .2s; }
.toggle input:checked + .track { background: #22c55e; }
.toggle input:checked + .track::after { left: 21px; } /* slide right */
Drag to choose a value
A range input is great for fuzzy values — volume, price, brightness. Pair it with an <output> so the current number is visible. Drag the sliders.
HTML
<label for="vol">Volume</label>
<input id="vol" type="range" min="0" max="100" value="60"
oninput="out.value = this.value">
<output id="out">60</output>
<label for="price">Max price</label>
<input id="price" type="range" min="0" max="500" step="10" value="250"
oninput="priceOut.value = '$' + this.value">
<output id="priceOut">$250</output>
CSS
input[type="range"] { width: 100%; accent-color: #38bdf8; } /* colored track + thumb */
output { font-weight: 700; color: #0369a1; }
JavaScript — live value + filled track
// Two ranges, two outputs. Update on every drag.
const vol = document.getElementById('vol');
const out = document.getElementById('out');
const price = document.getElementById('price');
const priceOut = document.getElementById('priceOut');
// Paint the track up to the thumb using a CSS gradient.
function paint(el) {
const pct = ((el.value - el.min) / (el.max - el.min)) * 100;
el.style.background =
'linear-gradient(to right, #38bdf8 0%, #38bdf8 ' + pct + '%, #e5e7eb ' + pct + '%, #e5e7eb 100%)';
}
vol.addEventListener('input', () => { out.value = vol.value; paint(vol); });
price.addEventListener('input', () => { priceOut.value = '$' + price.value; paint(price); });
paint(vol); paint(price); // initial state
Let users attach files
The default file input is hard to style, so hide it and trigger it from a styled <label>. A drag-and-drop zone is a friendlier alternative.
HTML
<input type="file" id="file"> <label class="upload" for="file">Choose a file</label> <label class="drop" for="file" id="drop">Drag a file here, or click to browse</label> <p class="help" id="fileName">No file chosen</p>
CSS
input[type="file"] { display: none; } /* hide the ugly default */
.upload {
display: inline-flex; gap: 8px;
border: 1px solid #cbd5e1; border-radius: 8px;
padding: 10px 16px; cursor: pointer; /* the styled trigger */
}
.drop { /* drag-and-drop zone */
display: block; text-align: center;
border: 2px dashed #cbd5e1; border-radius: 10px;
padding: 26px; margin-top: 12px;
color: #6b7280; cursor: pointer;
}
.drop.over { border-color: #38bdf8; color: #0369a1; background: #f0f9ff; }
.help { font-size: .78rem; color: #6b7280; }
JavaScript — show selected file name + drag highlight
// Grab the elements by the ids in the HTML above.
const file = document.getElementById('file');
const drop = document.getElementById('drop');
const name = document.getElementById('fileName');
// When a file is chosen, show its name and size.
file.addEventListener('change', () => {
if (!file.files.length) { name.textContent = 'No file chosen'; return; }
const f = file.files[0];
const kb = (f.size / 1024).toFixed(1);
name.textContent = f.name + ' (' + kb + ' KB)';
});
// Highlight the drop zone while a file is dragged over it.
['dragenter','dragover'].forEach(evt =>
drop.addEventListener(evt, e => { e.preventDefault(); drop.classList.add('over'); }));
['dragleave','drop'].forEach(evt =>
drop.addEventListener(evt, e => { e.preventDefault(); drop.classList.remove('over'); }));
// Accept the dropped file as if the user picked it from the dialog.
drop.addEventListener('drop', e => {
if (e.dataTransfer.files.length) {
file.files = e.dataTransfer.files;
file.dispatchEvent(new Event('change'));
}
});
input.files[0].name on the change event.One field, one button
A focused email capture — inline on wide screens, stacked on narrow ones. Keep it to a single field to maximize sign-ups.
HTML
<form class="newsletter" id="newsForm"> <input type="email" id="newsEmail" aria-label="Email" placeholder="you@example.com"> <button type="submit">Subscribe</button> </form> <p class="msg" id="newsMsg">Thanks for subscribing!</p>
CSS
.newsletter { display: flex; gap: 8px; } /* inline */
.newsletter input { flex: 1; }
.newsletter input { padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; }
.newsletter button { background: #38bdf8; color: #fff; border: 0; border-radius: 8px; padding: 10px 18px; font-weight: 600; cursor: pointer; }
.msg { display: none; color: #0369a1; font-weight: 600; margin-top: 10px; font-size: .9rem; }
.msg.show { display: block; }
.newsletter input.err { border-color: #ef4444; }
@media (max-width: 480px) {
.newsletter { flex-direction: column; } /* stack on phones */
}
JavaScript — validate email + show thank-you
// Grab the form, input, and confirmation message by the ids above.
const form = document.getElementById('newsForm');
const email = document.getElementById('newsEmail');
const msg = document.getElementById('newsMsg');
form.addEventListener('submit', (e) => {
e.preventDefault();
// Simple email check: something@something.something
const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim());
if (!ok) {
email.classList.add('err');
email.focus();
return;
}
email.classList.remove('err');
msg.textContent = 'Thanks for subscribing!';
msg.classList.add('show');
form.reset();
});
Break a long form into steps
Splitting a big form into steps with a progress bar feels lighter and improves completion. This version switches steps with pure CSS (radio inputs). Use Back / Next.
HTML
<input type="radio" name="ms" id="ms1" checked> <input type="radio" name="ms" id="ms2"> <input type="radio" name="ms" id="ms3"> <div class="bar"><span></span><span></span><span></span></div> <div class="pane m1"> <strong>Step 1 — Account</strong> <label for="msa">Email</label> <input id="msa" type="email" placeholder="you@example.com"> <label for="ms2">Next ›</label> </div> <div class="pane m2"> <strong>Step 2 — Profile</strong> <label for="msb">Display name</label> <input id="msb" type="text" placeholder="Jordan"> <label for="ms1">‹ Back</label> <label for="ms3">Next ›</label> </div> <div class="pane m3"> <strong>Step 3 — Done</strong> <p>Review your details and submit.</p> <label for="ms2">‹ Back</label> <button type="submit">Finish</button> </div>
CSS
.pane { display: none; }
#ms1:checked ~ .m1,
#ms2:checked ~ .m2,
#ms3:checked ~ .m3 { display: block; } /* show the active step */
.bar { display: flex; gap: 6px; margin-bottom: 14px; }
.bar span { flex: 1; height: 6px; border-radius: 3px; background: #e5e7eb; }
Email, password, and a way back in
A focused sign-in form: just email and password, a “remember me” and “forgot password” row, one clear primary button, and a link to register. Click the eye to show/hide the password.
HTML
<form>
<label for="email">Email</label>
<input id="email" type="email" autocomplete="email" required>
<label for="pw">Password</label>
<div class="pw-wrap">
<input id="pw" type="password" autocomplete="current-password" required>
<button type="button" class="peek" data-target="pw" aria-label="Show password"><i class="ph ph-eye"></i></button>
</div>
<div class="between">
<label><input type="checkbox"> Remember me</label>
<a href="#">Forgot password?</a>
</div>
<button type="submit" class="block">Log in</button>
<div class="divider">or</div>
<button type="button" class="social-btn"><i class="ph ph-google-logo"></i> Continue with Google</button>
<p class="meta">New here? <a href="#">Create an account</a></p>
</form>
CSS — position the eye button inside the field
.pw-wrap { position: relative; }
.pw-wrap input { width: 100%; padding: 10px 12px; padding-right: 40px; /* room for the icon */
border: 1px solid #cbd5e1; border-radius: 8px; font-size: .92rem; }
.pw-wrap .peek { position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
background: none; border: 0; color: #6b7280; cursor: pointer; font-size: 1.1rem; padding: 0; }
JavaScript — show / hide password
// Grab the elements by the ids/classes in the HTML above.
const input = document.getElementById('pw');
const peek = document.querySelector('.peek');
peek.addEventListener('click', () => {
// Flip between a masked password field and a readable text field.
const show = input.type === 'password';
input.type = show ? 'text' : 'password';
// Swap the icon so it matches the current state.
peek.innerHTML = show
? '<i class="ph ph-eye-slash"></i>'
: '<i class="ph ph-eye"></i>';
});
<label>s and the matching autocomplete values (email, current-password) so password managers and browsers can fill the form correctly.Sign up with a password strength meter
A registration form asks for a bit more — name, email, a password (with a live strength meter), and agreement to terms. Type a password to watch the meter respond.
HTML
<label for="pw">Password</label> <input id="pw" type="password" autocomplete="new-password" placeholder="At least 8 characters"> <!-- the meter: a track with an inner bar that grows --> <div class="strength"><i id="bar"></i></div> <p class="strength-lbl" id="lbl">Password strength</p> <label><input type="checkbox"> I agree to the <a href="#registration">Terms & Privacy Policy</a></label> <button class="btn block" type="submit">Create account</button> <p class="meta">Already have an account? <a href="#login">Log in</a></p>
CSS
input { width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: .92rem; }
.strength { height: 6px; border-radius: 999px; background: #e5e7eb; margin-top: 8px; overflow: hidden; }
.strength i { display: block; height: 100%; width: 0; background: #ef4444; transition: width .2s, background .2s; }
.strength-lbl { font-size: .74rem; color: #6b7280; margin-top: 4px; }
JavaScript — simple strength meter
// Grab the elements by the ids in the HTML above.
const pw = document.getElementById('pw');
const bar = document.getElementById('bar');
const lbl = document.getElementById('lbl');
pw.addEventListener('input', () => {
let score = 0;
if (pw.value.length >= 8) score++;
if (/[A-Z]/.test(pw.value)) score++;
if (/[0-9]/.test(pw.value)) score++;
if (/[^A-Za-z0-9]/.test(pw.value)) score++; // symbol
const pct = [0, 25, 50, 75, 100][score];
const color = ['#ef4444','#ef4444','#f59e0b','#3b82f6','#22c55e'][score];
const text = ['Password strength','Weak','Fair','Good','Strong'][score];
bar.style.width = pct + '%';
bar.style.background = color;
lbl.textContent = pw.value ? text : 'Password strength';
});
autocomplete="new-password" on sign-up so browsers offer to generate & save a strong password rather than autofilling the old one.The buttons that finish a form
Every form ends in actions. The submit button is the primary call to action; a reset clears the fields; a disabled state prevents invalid sends; and a loading state stops double-submits. Click “Submit” to see the loading state.
HTML — use real button types inside a form
<form> <button class="btn" type="submit" id="submitBtn">Submit</button> <!-- sends the form --> <button class="btn secondary" type="button">Save draft</button> <button class="btn reset" type="reset">Reset</button> <!-- clears all fields --> <button class="btn" type="button" disabled>Disabled</button> <!-- can't submit yet --> </form>
CSS — states
.btn { background: #38bdf8; color: #fff; border: none; border-radius: 8px; padding: 10px 18px; cursor: pointer; }
.btn.secondary { background: #eef2f6; color: #1f2937; } /* low-emphasis */
.btn.reset { background: #fff; border: 1px solid #cbd5e1; color: #374151; } /* secondary look */
.btn:disabled { background: #cbd5e1; cursor: not-allowed; } /* can't submit yet */
.btn.loading { opacity: .75; pointer-events: none; } /* mid-request */
JavaScript — loading state (prevents double-submit)
// Grab the button by the id in the HTML above.
const btn = document.getElementById('submitBtn');
btn.addEventListener('click', () => {
const orig = btn.textContent;
btn.disabled = true;
btn.classList.add('loading');
btn.textContent = 'Submitting…';
// Simulate a network request, then restore the button.
// In real code you'd do this when your fetch() resolves.
setTimeout(() => {
btn.disabled = false;
btn.classList.remove('loading');
btn.textContent = orig;
}, 1500);
});
The whole thing as one file
Many of the patterns above — text fields with states, a select, a textarea, radios, checkboxes, a toggle, a range slider, native date/time, file upload, and submit/reset buttons — combined into one complete HTML file. Copy it into a new file called contact.html, open it in a browser, and it just works — no libraries, no build step. Here's the live result, then the full code.
Live preview
Complete HTML — copy this whole file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact & Preferences Form</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f6f7fb; color: #1f2433; padding: 32px 16px; }
.form-card { max-width: 480px; margin: 0 auto; background: #fff; border: 1px solid #e5e7eb; border-radius: 16px; padding: 28px; }
.form-card h1 { font-size: 1.5rem; margin-bottom: 4px; }
.form-card .intro { color: #6b7280; font-size: .9rem; margin-bottom: 20px; }
fieldset { border: none; padding: 0; margin-bottom: 18px; }
legend { font-weight: 600; font-size: .85rem; color: #374151; margin-bottom: 8px; }
.field { margin-bottom: 16px; }
label.lbl { display: block; font-size: .82rem; font-weight: 600; color: #374151; margin-bottom: 6px; }
input[type="text"], input[type="email"], input[type="date"], input[type="time"],
select, textarea {
width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px;
font: inherit; color: #111; background: #fff;
}
input:focus, select:focus, textarea:focus { outline: 2px solid #38bdf8; outline-offset: 1px; border-color: #38bdf8; }
input.err { border-color: #ef4444; }
input:disabled { background: #f1f5f9; color: #9ca3af; cursor: not-allowed; }
textarea { min-height: 90px; resize: vertical; }
.help { font-size: .78rem; color: #9aa3b2; margin-top: 4px; }
.err-msg { font-size: .78rem; color: #ef4444; margin-top: 4px; }
.row2 { display: flex; gap: 14px; }
.row2 .field { flex: 1; }
.opt { display: flex; align-items: center; gap: 9px; margin: 8px 0; font-size: .9rem; color: #374151; }
.opt input { width: auto; accent-color: #38bdf8; }
/* toggle switch */
.toggle { display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: .9rem; color: #374151; margin: 8px 0; }
.toggle input { display: none; }
.track { width: 44px; height: 26px; border-radius: 999px; background: #cbd5e1; position: relative; flex: none; transition: background .2s; }
.track::after { content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; transition: left .2s; }
.toggle input:checked + .track { background: #22c55e; }
.toggle input:checked + .track::after { left: 21px; }
/* range */
.range { display: flex; align-items: center; gap: 12px; }
.range input[type="range"] { flex: 1; accent-color: #38bdf8; }
.range output { font-weight: 700; color: #0369a1; min-width: 34px; text-align: right; }
/* file upload */
.file input[type="file"] { display: none; }
.file .up { display: inline-flex; align-items: center; gap: 8px; background: #fff; border: 1px solid #cbd5e1; border-radius: 8px; padding: 10px 16px; cursor: pointer; font-weight: 600; color: #374151; }
.file .up:hover { border-color: #38bdf8; }
.file .name { font-size: .8rem; color: #6b7280; margin-top: 6px; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.btn { background: #38bdf8; color: #fff; border: none; border-radius: 8px; padding: 10px 18px; font-weight: 600; cursor: pointer; font: inherit; }
.btn.reset { background: #fff; border: 1px solid #cbd5e1; color: #374151; }
.btn:disabled { background: #cbd5e1; cursor: not-allowed; }
.done { display: none; background: #ecfdf5; border: 1px solid #6ee7b7; color: #065f46; border-radius: 8px; padding: 12px 14px; font-size: .9rem; margin-top: 14px; }
.done.show { display: block; }
</style>
</head>
<body>
<form class="form-card" id="contactForm">
<h1>Get in touch</h1>
<p class="intro">A few fields, a few choices — built from native form controls.</p>
<div class="row2">
<div class="field">
<label class="lbl" for="first">First name</label>
<input id="first" type="text" placeholder="Jordan">
</div>
<div class="field">
<label class="lbl" for="last">Last name</label>
<input id="last" type="text" placeholder="Parker">
</div>
</div>
<div class="field">
<label class="lbl" for="email">Email</label>
<input id="email" type="email" placeholder="you@example.com" autocomplete="email">
<p class="help">We'll only use this to reply.</p>
</div>
<div class="field">
<label class="lbl" for="topic">Topic</label>
<select id="topic">
<option>General question</option>
<option>Feedback</option>
<option>Bug report</option>
<option>Something else</option>
</select>
</div>
<div class="row2">
<div class="field">
<label class="lbl" for="date">Preferred date</label>
<input id="date" type="date">
</div>
<div class="field">
<label class="lbl" for="time">Preferred time</label>
<input id="time" type="time" value="09:00" step="900">
</div>
</div>
<div class="field">
<label class="lbl" for="msg">Message</label>
<textarea id="msg" maxlength="500" placeholder="Write your message…"></textarea>
<p class="help">Up to 500 characters. Drag the corner to resize.</p>
</div>
<fieldset>
<legend>How urgent is it?</legend>
<label class="opt"><input type="radio" name="urgency" checked> Whenever you can</label>
<label class="opt"><input type="radio" name="urgency"> This week</label>
<label class="opt"><input type="radio" name="urgency"> As soon as possible</label>
</fieldset>
<fieldset>
<legend>Satisfaction so far</legend>
<div class="range">
<input id="rating" type="range" min="0" max="10" value="8">
<output id="ratingOut">8</output>
</div>
</fieldset>
<div class="field file">
<label class="lbl">Attach a file (optional)</label>
<input type="file" id="file">
<label class="up" for="file">Choose a file</label>
<p class="name" id="fileName">No file chosen</p>
</div>
<label class="toggle">
<input type="checkbox" id="copyMe">
<span class="track"></span> Email me a copy
</label>
<label class="opt"><input type="checkbox" id="agree"> I agree to be contacted</label>
<div class="btn-row">
<button class="btn" type="submit" id="send">Send message</button>
<button class="btn reset" type="reset">Reset</button>
</div>
<div class="done" id="done">Thanks — your message has been sent.</div>
</form>
<script>
/* live range value */
var rating = document.getElementById('rating');
var ratingOut = document.getElementById('ratingOut');
rating.addEventListener('input', function () { ratingOut.value = rating.value; });
/* show chosen file name */
var file = document.getElementById('file');
var fileName = document.getElementById('fileName');
file.addEventListener('change', function () {
fileName.textContent = file.files.length ? file.files[0].name : 'No file chosen';
});
/* fake submit: loading state, then success */
var form = document.getElementById('contactForm');
var send = document.getElementById('send');
var done = document.getElementById('done');
form.addEventListener('submit', function (e) {
e.preventDefault();
send.disabled = true;
send.textContent = 'Sending…';
setTimeout(function () {
send.disabled = false;
send.textContent = 'Send message';
done.classList.add('show');
}, 1200);
});
form.addEventListener('reset', function () {
done.classList.remove('show');
ratingOut.value = 8;
fileName.textContent = 'No file chosen';
});
</script>
</body>
</html>
Keep building
For inputs, validation, and layout see the Forms tutorial and Forms Lab; wire up submissions with Formspree; or browse the full Component Library.