← Component Library
Form Components

Form Patterns

Common form types beyond the basics — surveys, search, and native date/time pickers. Each has a working demo plus the HTML & CSS.

01 · Survey Form

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.

How likely are you to recommend us? (1–5)

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;
}
To actually receive responses, point the form at a service like Formspree — no backend required.
02 · Search Form

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;
}
03 · Date Picker

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; }
04 · Time Picker

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 */
Native date & time inputs are accessible and free — reach for a JavaScript library only when you need custom styling or ranges the browser can't do.
05 · Text Input

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.

3–20 characters, letters and numbers.
Enter a valid email address.
Assigned automatically — can't be changed.

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 */
Use the right type (email, tel, url, number) — mobile keyboards adapt and the browser validates for free.
06 · Textarea

Multi-line text

A <textarea> is for longer input — comments, messages, bios. Let users drag to resize vertically, and hint at the limit.

Up to 500 characters. Drag the corner to resize.

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; }
07 · Dropdown / Select

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.

Hold Ctrl / Cmd to select more than one.

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;
}
08 · Checkbox

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.

Pizza toppings

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 */
09 · Radio Button

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.

Plan
Ship to

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 */
10 · Toggle Switch

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 */
11 · Range Slider

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.

60
$250

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
12 · File Upload

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.

No file chosen

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'));
  }
});
Showing the chosen file name takes one line of JS: read input.files[0].name on the change event.
13 · Newsletter Signup

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();
});
14 · Multi-Step Form

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.

Step 1 — Account
Step 2 — Profile
Step 3 — Done

Review your details and submit.

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; }
Pure CSS is fine for a demo; a real multi-step form usually validates each step with JavaScript before letting you advance.
15 · Login Form

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.

Forgot password?
or

New here? Create an account

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>';
});
Accessibility: use real <label>s and the matching autocomplete values (email, current-password) so password managers and browsers can fill the form correctly.
16 · Registration Form

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.

Password strength

Already have an account? Log in

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 &amp; 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';
});
Use autocomplete="new-password" on sign-up so browsers offer to generate & save a strong password rather than autofilling the old one.
17 · Submit & Reset Buttons

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);
});

Reset button note: <button type="reset"> clears the form back to its defaults instantly — useful, but place it away from Submit so it isn't clicked by accident, and skip it on short forms where it adds little.

18 · Full Page

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 &amp; 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>
That's a real, standalone page. The live preview above is rendered from the exact code in the box — so what you copy is precisely what you see.
19 · Related

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.