Examples

Contact form examples

Fourteen contact forms, from the three-field classic to a two-step form and a file upload. Each one has a preview, a note on when it fits, and HTML you can paste into a page today.

Short answer: Each example below is plain HTML with labels, the right input types, autocomplete and a hidden spam trap. The form posts to Formgong. Replace fk_your_access_key with your own key and messages reach your inbox. Plain forms show a thank-you page only after the message is saved; the ones with a script say sent only when the server confirms.

How to use these examples

Pick the closest one, press Copy and paste it where the form should go. Every example carries its own small <style> block, and each rule starts with the form's id, so it will not restyle the rest of your page. Delete the block if you want your site's CSS instead.

The code posts to Formgong. Before it delivers anything, replace fk_your_access_key with the key of a free Formgong form. Then send yourself a test. Messages go to your inbox and, if you connect it, to Telegram.

Two kinds of submit are used. Most examples are plain HTML: the browser posts the fields and Formgong answers with a thank-you page, which it shows only after the message is saved. If the key is wrong or a check fails, the visitor gets an error page with the reason. The feedback, newsletter and multi-step examples keep the visitor on your page instead. Their script reads Formgong's JSON answer and prints a success line only when it says success: true.

All fourteen share the basics: a label for every field, a name on every field, type="email" with autocomplete="email", required where an empty answer is useless, and a hidden botcheck field that catches simple bots without a puzzle.

1. Simple contact form

Preview

Name, email, message. Start here unless you already know you need more: each extra field is one more thing to fill in before someone gives up. No script, so it works on any page that accepts HTML. After Send, the visitor sees Formgong's thank-you page, and that page appears only once the message is saved.

Simple contact form: HTML
<style>
#cf-simple { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-simple label, #cf-simple legend { display: grid; gap: 6px; font-weight: 600; }
#cf-simple input, #cf-simple select, #cf-simple textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-simple textarea { min-height: 120px; resize: vertical; }
#cf-simple :focus-visible { outline: 2px solid #2f5bea; outline-offset: 2px; }
#cf-simple button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #2f5bea; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-simple button:disabled { opacity: 0.6; cursor: wait; }
</style>
<form id="cf-simple" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <label for="simple-name">Name
    <input id="simple-name" name="name" autocomplete="name" required>
  </label>
  <label for="simple-email">Email
    <input id="simple-email" type="email" name="email" autocomplete="email" required>
  </label>
  <label for="simple-message">Message
    <textarea id="simple-message" name="message" required></textarea>
  </label>
  <button type="submit">Send message</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

2. Contact form with a phone number

Preview

Best way to reach you

For trades, clinics and anyone who would rather call back than type a reply. The phone stays optional, and the radio buttons tell you which channel the person prefers. type="tel" brings up the number keypad on phones. It does not check the format, because phone numbers differ too much from country to country.

Contact form with a phone number: HTML
<style>
#cf-phone { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-phone label, #cf-phone legend { display: grid; gap: 6px; font-weight: 600; }
#cf-phone input, #cf-phone select, #cf-phone textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-phone textarea { min-height: 120px; resize: vertical; }
#cf-phone :focus-visible { outline: 2px solid #2f5bea; outline-offset: 2px; }
#cf-phone button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #2f5bea; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-phone button:disabled { opacity: 0.6; cursor: wait; }
#cf-phone fieldset { display: flex; gap: 18px; margin: 0; padding: 0; border: 0; }
#cf-phone fieldset label { display: flex; gap: 6px; align-items: center; font-weight: 400; }
</style>
<form id="cf-phone" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <label for="phone-name">Name
    <input id="phone-name" name="name" autocomplete="name" required>
  </label>
  <label for="phone-email">Email
    <input id="phone-email" type="email" name="email" autocomplete="email" required>
  </label>
  <label for="phone-tel">Phone (optional)
    <input id="phone-tel" type="tel" name="phone" autocomplete="tel">
  </label>
  <fieldset>
    <legend>Best way to reach you</legend>
    <label><input type="radio" name="contact_by" value="Email" checked> Email</label>
    <label><input type="radio" name="contact_by" value="Phone"> Phone</label>
  </fieldset>
  <label for="phone-message">Message
    <textarea id="phone-message" name="message" required></textarea>
  </label>
  <button type="submit">Send</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

3. Contact form with a subject dropdown

Preview

Useful when different people answer different questions. The first option is empty and the select is required, so nobody sends "Sales question" just because it was preselected. The select is named subject: when the form has no hidden _subject and no subject set in the dashboard, Formgong uses this answer as the subject of the notification email, so you can sort mail by it.

Contact form with a subject dropdown: HTML
<style>
#cf-subject { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-subject label, #cf-subject legend { display: grid; gap: 6px; font-weight: 600; }
#cf-subject input, #cf-subject select, #cf-subject textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-subject textarea { min-height: 120px; resize: vertical; }
#cf-subject :focus-visible { outline: 2px solid #2f5bea; outline-offset: 2px; }
#cf-subject button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #2f5bea; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-subject button:disabled { opacity: 0.6; cursor: wait; }
</style>
<form id="cf-subject" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <label for="subject-name">Name
    <input id="subject-name" name="name" autocomplete="name" required>
  </label>
  <label for="subject-email">Email
    <input id="subject-email" type="email" name="email" autocomplete="email" required>
  </label>
  <label for="subject-topic">What is it about?
    <select id="subject-topic" name="subject" required>
      <option value="">Choose one</option>
      <option>Sales question</option>
      <option>Help with an order</option>
      <option>Press</option>
      <option>Something else</option>
    </select>
  </label>
  <label for="subject-message">Message
    <textarea id="subject-message" name="message" required></textarea>
  </label>
  <button type="submit">Send</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

4. Quote request form

Preview

A quote needs scope and timing more than it needs a long message. The service and timeline lists give you both before the first reply. The hidden _subject puts "New quote request" in the email subject, so these do not get lost among ordinary contact messages.

Quote request form: HTML
<style>
#cf-quote { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-quote label, #cf-quote legend { display: grid; gap: 6px; font-weight: 600; }
#cf-quote input, #cf-quote select, #cf-quote textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-quote textarea { min-height: 120px; resize: vertical; }
#cf-quote :focus-visible { outline: 2px solid #0f766e; outline-offset: 2px; }
#cf-quote button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #0f766e; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-quote button:disabled { opacity: 0.6; cursor: wait; }
</style>
<form id="cf-quote" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_subject" value="New quote request">
  <label for="quote-name">Name
    <input id="quote-name" name="name" autocomplete="name" required>
  </label>
  <label for="quote-email">Work email
    <input id="quote-email" type="email" name="email" autocomplete="email" required>
  </label>
  <label for="quote-company">Company (optional)
    <input id="quote-company" name="company" autocomplete="organization">
  </label>
  <label for="quote-service">Service
    <select id="quote-service" name="service" required>
      <option value="">Choose one</option>
      <option>Website</option>
      <option>Online shop</option>
      <option>Maintenance</option>
    </select>
  </label>
  <label for="quote-when">When do you need it?
    <select id="quote-when" name="timeline">
      <option>As soon as possible</option>
      <option>Within a month</option>
      <option>Later this year</option>
    </select>
  </label>
  <label for="quote-details">What should the quote cover?
    <textarea id="quote-details" name="details" required></textarea>
  </label>
  <button type="submit">Request a quote</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

5. Booking request form

Preview

This asks for a time. It does not reserve one, and nothing on your calendar changes. You read the request and confirm by email or phone. That is fine for a small studio or a practice that books a few people a day. If double bookings are a real risk, use a calendar tool instead.

Booking request form: HTML
<style>
#cf-booking { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-booking label, #cf-booking legend { display: grid; gap: 6px; font-weight: 600; }
#cf-booking input, #cf-booking select, #cf-booking textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-booking textarea { min-height: 120px; resize: vertical; }
#cf-booking :focus-visible { outline: 2px solid #7c3aed; outline-offset: 2px; }
#cf-booking button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #7c3aed; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-booking button:disabled { opacity: 0.6; cursor: wait; }
#cf-booking .cf-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
</style>
<form id="cf-booking" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <label for="booking-name">Name
    <input id="booking-name" name="name" autocomplete="name" required>
  </label>
  <label for="booking-email">Email
    <input id="booking-email" type="email" name="email" autocomplete="email" required>
  </label>
  <label for="booking-phone">Phone
    <input id="booking-phone" type="tel" name="phone" autocomplete="tel" required>
  </label>
  <label for="booking-service">Service
    <select id="booking-service" name="service" required>
      <option value="">Choose one</option>
      <option>First visit</option>
      <option>Follow-up</option>
    </select>
  </label>
  <div class="cf-row">
  <label for="booking-date">Preferred date
    <input id="booking-date" type="date" name="preferred_date" required>
  </label>
  <label for="booking-time">Time of day
    <select id="booking-time" name="time_of_day">
      <option>Morning</option>
      <option>Afternoon</option>
      <option>Evening</option>
    </select>
  </label>
  </div>
  <button type="submit">Request this time</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

6. Support and bug report form

Preview

How bad is it?

The questions you would otherwise ask in the second email: which page, how bad, what they did. The severity buttons let you open the "I cannot continue" reports first. type="url" makes the browser reject text that is not a web address, so the field is optional rather than a wall in front of someone who cannot find the link.

Support and bug report form: HTML
<style>
#cf-support { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-support label, #cf-support legend { display: grid; gap: 6px; font-weight: 600; }
#cf-support input, #cf-support select, #cf-support textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-support textarea { min-height: 120px; resize: vertical; }
#cf-support :focus-visible { outline: 2px solid #b45309; outline-offset: 2px; }
#cf-support button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #b45309; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-support button:disabled { opacity: 0.6; cursor: wait; }
#cf-support fieldset { display: grid; gap: 6px; margin: 0; padding: 0; border: 0; }
#cf-support fieldset label { display: flex; gap: 8px; align-items: center; font-weight: 400; }
</style>
<form id="cf-support" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_subject" value="Bug report">
  <label for="support-email">Your email
    <input id="support-email" type="email" name="email" autocomplete="email" required>
  </label>
  <label for="support-url">Page where it happened
    <input id="support-url" type="url" name="page_url" placeholder="https://">
  </label>
  <fieldset>
    <legend>How bad is it?</legend>
    <label><input type="radio" name="severity" value="I cannot continue" required> I cannot continue</label>
    <label><input type="radio" name="severity" value="Annoying"> Annoying, but I can work around it</label>
    <label><input type="radio" name="severity" value="Cosmetic"> It just looks wrong</label>
  </fieldset>
  <label for="support-what">What happened?
    <textarea id="support-what" name="what_happened" required></textarea>
  </label>
  <label for="support-steps">Steps to see it again (optional)
    <textarea id="support-steps" name="steps"></textarea>
  </label>
  <label for="support-browser">Browser and device (optional)
    <input id="support-browser" name="browser" autocomplete="off">
  </label>
  <button type="submit">Send report</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

7. Feedback form with a 1 to 5 rating

Preview

How was it? (1 = poor, 5 = great)

One required click, everything else optional. The scores are real radio buttons, so keyboards and screen readers handle them, and the boxes are only styling on top. This one stays on the page: a short script sends it with fetch and writes "Thank you" only when Formgong answers success: true. A rejected or failed send shows the reason instead.

Feedback form with a 1 to 5 rating: HTML
<style>
#cf-rating { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-rating label, #cf-rating legend { display: grid; gap: 6px; font-weight: 600; }
#cf-rating input, #cf-rating select, #cf-rating textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-rating textarea { min-height: 120px; resize: vertical; }
#cf-rating :focus-visible { outline: 2px solid #c2410c; outline-offset: 2px; }
#cf-rating button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #c2410c; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-rating button:disabled { opacity: 0.6; cursor: wait; }
#cf-rating fieldset { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: 0; border: 0; }
#cf-rating legend { margin-bottom: 8px; }
#cf-rating .cf-rate { position: relative; display: block; }
#cf-rating .cf-rate input { position: absolute; opacity: 0; }
#cf-rating .cf-rate span { display: grid; place-items: center; width: 44px; height: 44px; border: 1px solid #9aa5b1; border-radius: 8px; font-weight: 600; cursor: pointer; }
#cf-rating .cf-rate input:checked + span { background: #c2410c; border-color: #c2410c; color: #fff; }
#cf-rating .cf-rate input:focus-visible + span { outline: 2px solid #c2410c; outline-offset: 2px; }
#cf-rating p { margin: 0; }
</style>
<form id="cf-rating" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_subject" value="New feedback">
  <fieldset>
    <legend>How was it? (1 = poor, 5 = great)</legend>
    <label class="cf-rate"><input type="radio" name="rating" value="1" required><span>1</span></label>
    <label class="cf-rate"><input type="radio" name="rating" value="2"><span>2</span></label>
    <label class="cf-rate"><input type="radio" name="rating" value="3"><span>3</span></label>
    <label class="cf-rate"><input type="radio" name="rating" value="4"><span>4</span></label>
    <label class="cf-rate"><input type="radio" name="rating" value="5"><span>5</span></label>
  </fieldset>
  <label for="rating-comment">What should we change? (optional)
    <textarea id="rating-comment" name="comment"></textarea>
  </label>
  <label for="rating-email">Email, if you want an answer (optional)
    <input id="rating-email" type="email" name="email" autocomplete="email">
  </label>
  <button type="submit">Send feedback</button>
  <p role="status" tabindex="-1"></p>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>
<script>
document.getElementById("cf-rating").addEventListener("submit", async (event) => {
  event.preventDefault();
  const form = event.currentTarget;
  const status = form.querySelector("[role=status]");
  const button = form.querySelector("button[type=submit]");
  button.disabled = true;
  status.textContent = "Sending…";
  try {
    const response = await fetch(form.action, {
      method: "POST",
      body: new FormData(form),
      headers: { Accept: "application/json" },
    });
    const data = await response.json();
    if (data.success === true) {
      form.reset();
      status.textContent = "Thank you. Your feedback arrived.";
    } else {
      status.textContent = data.message || "Not sent. Please try again.";
    }
  } catch {
    status.textContent = "Not sent: check your connection and try again.";
  }
  button.disabled = false;
});
</script>

8. Newsletter signup form

Preview

One email a month. Reply "stop" and we take you off the list.

An email field and a button, sized for a footer or a sidebar. Formgong collects the addresses. It does not send your newsletter, so connect the form to Google Sheets or a webhook and import from there into the mailing tool you use. Change the promise in the small print to match what you will really send.

Newsletter signup form: HTML
<style>
#cf-news { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-news label, #cf-news legend { display: grid; gap: 6px; font-weight: 600; }
#cf-news input, #cf-news select, #cf-news textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-news textarea { min-height: 120px; resize: vertical; }
#cf-news :focus-visible { outline: 2px solid #15803d; outline-offset: 2px; }
#cf-news button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #15803d; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-news button:disabled { opacity: 0.6; cursor: wait; }
#cf-news .cf-inline { display: flex; flex-wrap: wrap; gap: 8px; }
#cf-news .cf-inline input { flex: 1 1 14rem; }
#cf-news p { margin: 0; font-size: 14px; color: #52606d; }
</style>
<form id="cf-news" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_subject" value="Newsletter signup">
  <label for="news-email">Get the monthly update</label>
  <div class="cf-inline">
    <input id="news-email" type="email" name="email" autocomplete="email" placeholder="you@example.com" required>
    <button type="submit">Subscribe</button>
  </div>
  <p>One email a month. Reply "stop" and we take you off the list.</p>
  <p role="status" tabindex="-1"></p>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>
<script>
document.getElementById("cf-news").addEventListener("submit", async (event) => {
  event.preventDefault();
  const form = event.currentTarget;
  const status = form.querySelector("[role=status]");
  const button = form.querySelector("button[type=submit]");
  button.disabled = true;
  status.textContent = "Sending…";
  try {
    const response = await fetch(form.action, {
      method: "POST",
      body: new FormData(form),
      headers: { Accept: "application/json" },
    });
    const data = await response.json();
    if (data.success === true) {
      form.reset();
      status.textContent = "You are on the list.";
    } else {
      status.textContent = data.message || "Not sent. Please try again.";
    }
  } catch {
    status.textContent = "Not sent: check your connection and try again.";
  }
  button.disabled = false;
});
</script>

9. Contact form with file upload

Preview

The Cloudflare Turnstile check shows here.

For a brief, a photo of the damage, a CV. Two attributes do the work: method="POST" and enctype="multipart/form-data". Without the second, the browser sends the file name and drops the file. On Formgong, turn on file attachments in the form settings, verify your email and add your Turnstile site key: posts with files are refused without the Turnstile check. The limits are PDF, PNG or JPEG, 3 files, 5 MiB each and 10 MiB in total.

Contact form with file upload: HTML
<style>
#cf-upload { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-upload label, #cf-upload legend { display: grid; gap: 6px; font-weight: 600; }
#cf-upload input, #cf-upload select, #cf-upload textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-upload textarea { min-height: 120px; resize: vertical; }
#cf-upload :focus-visible { outline: 2px solid #2f5bea; outline-offset: 2px; }
#cf-upload button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #2f5bea; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-upload button:disabled { opacity: 0.6; cursor: wait; }
#cf-upload input[type=file] { padding: 8px; }
#cf-upload small { font-weight: 400; color: #52606d; }
</style>
<form id="cf-upload" action="https://formgong.com/submit" method="POST" enctype="multipart/form-data">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <label for="upload-name">Name
    <input id="upload-name" name="name" autocomplete="name" required>
  </label>
  <label for="upload-email">Email
    <input id="upload-email" type="email" name="email" autocomplete="email" required>
  </label>
  <label for="upload-message">Message
    <textarea id="upload-message" name="message" required></textarea>
  </label>
  <label for="upload-file">Attach files <small>PDF, PNG or JPEG, up to 3 files</small>
    <input id="upload-file" type="file" name="attachment" accept=".pdf,.png,.jpg,.jpeg" multiple>
  </label>
  <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
  <div class="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITE_KEY"></div>
  <button type="submit">Send</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

10. Multi-step contact form

Preview

Step 1 of 2: about you

Two short screens instead of one long one, for forms with more than five or six questions. It is still a single form and a single POST at the end: the script only hides and shows the two fieldsets and checks step 1 before moving on. Without JavaScript, both steps show at once and the form still sends. The preview shows step 1.

Multi-step contact form: HTML
<style>
#cf-steps { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-steps label, #cf-steps legend { display: grid; gap: 6px; font-weight: 600; }
#cf-steps input, #cf-steps select, #cf-steps textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-steps textarea { min-height: 120px; resize: vertical; }
#cf-steps :focus-visible { outline: 2px solid #0369a1; outline-offset: 2px; }
#cf-steps button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #0369a1; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-steps button:disabled { opacity: 0.6; cursor: wait; }
#cf-steps fieldset { display: grid; gap: 14px; margin: 0; padding: 0; border: 0; }
#cf-steps legend { margin-bottom: 4px; font-size: 14px; color: #52606d; }
#cf-steps .cf-buttons { display: flex; gap: 8px; }
#cf-steps [data-back] { background: #e4e7eb; color: #1f2933; }
#cf-steps [hidden] { display: none; }
</style>
<form id="cf-steps" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <fieldset data-step="1">
    <legend>Step 1 of 2: about you</legend>
  <label for="steps-name">Name
    <input id="steps-name" name="name" autocomplete="name" required>
  </label>
  <label for="steps-email">Email
    <input id="steps-email" type="email" name="email" autocomplete="email" required>
  </label>
  </fieldset>
  <fieldset data-step="2">
    <legend>Step 2 of 2: the project</legend>
  <label for="steps-type">Project type
    <select id="steps-type" name="project_type" required>
      <option value="">Choose one</option>
      <option>New website</option>
      <option>Redesign</option>
      <option>Small fixes</option>
    </select>
  </label>
  <label for="steps-details">Details
    <textarea id="steps-details" name="details" required></textarea>
  </label>
  </fieldset>
  <div class="cf-buttons">
    <button type="button" data-back hidden>Back</button>
    <button type="button" data-next hidden>Next</button>
    <button type="submit">Send</button>
  </div>
  <p role="status" tabindex="-1"></p>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>
<script>
(() => {
  const form = document.getElementById("cf-steps");
  const steps = form.querySelectorAll("fieldset[data-step]");
  const next = form.querySelector("[data-next]");
  const back = form.querySelector("[data-back]");
  const send = form.querySelector("button[type=submit]");
  const status = form.querySelector("[role=status]");
  function show(index) {
    steps.forEach((step, i) => { step.hidden = i !== index; });
    next.hidden = index !== 0;
    back.hidden = index === 0;
    send.hidden = index === 0;
  }
  show(0);
  next.addEventListener("click", () => {
    for (const input of steps[0].querySelectorAll("input, select, textarea")) {
      if (!input.reportValidity()) return;
    }
    show(1);
    steps[1].querySelector("input, select, textarea").focus();
  });
  back.addEventListener("click", () => show(0));
  form.addEventListener("submit", async (event) => {
    event.preventDefault();
    send.disabled = true;
    status.textContent = "Sending…";
    try {
      const response = await fetch(form.action, {
        method: "POST",
        body: new FormData(form),
        headers: { Accept: "application/json" },
      });
      const data = await response.json();
      if (data.success === true) {
        form.reset();
        show(0);
        status.textContent = "Thanks. Your message arrived.";
      } else {
        status.textContent = data.message || "Not sent. Please try again.";
      }
    } catch {
      status.textContent = "Not sent: check your connection and try again.";
    }
    send.disabled = false;
  });
})();
</script>

12. Dark contact form

Preview

The simple form again, styled for a dark site. The button is a light blue with near-black text rather than white on blue, because white on a saturated color loses contrast on dark backgrounds. The input borders are lighter than the background, otherwise the boxes disappear. Check the focus outline too: the default one is often hard to see on dark.

Dark contact form: HTML
<style>
#cf-dark { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-dark label, #cf-dark legend { display: grid; gap: 6px; font-weight: 600; }
#cf-dark input, #cf-dark select, #cf-dark textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-dark textarea { min-height: 120px; resize: vertical; }
#cf-dark :focus-visible { outline: 2px solid #8ab4ff; outline-offset: 2px; }
#cf-dark button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #8ab4ff; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-dark button:disabled { opacity: 0.6; cursor: wait; }
#cf-dark { padding: 24px; border-radius: 12px; background: #11161d; color: #e6e9ee; }
#cf-dark input, #cf-dark textarea { border-color: #3b4552; background: #1a212b; color: #e6e9ee; }
#cf-dark button { background: #8ab4ff; color: #0b1220; }
</style>
<form id="cf-dark" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <label for="dark-name">Name
    <input id="dark-name" name="name" autocomplete="name" required>
  </label>
  <label for="dark-email">Email
    <input id="dark-email" type="email" name="email" autocomplete="email" required>
  </label>
  <label for="dark-message">Message
    <textarea id="dark-message" name="message" required></textarea>
  </label>
  <button type="submit">Send</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

13. One-field form, no name or email

Preview

A suggestion box for a team page, a workshop or an event. It does not ask who is writing, so more people answer honestly. The catch: there is no email field, so the notification has no reply-to address and you cannot answer. If you need to answer, use the simple form above.

One-field form, no name or email: HTML
<style>
#cf-note { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-note label, #cf-note legend { display: grid; gap: 6px; font-weight: 600; }
#cf-note input, #cf-note select, #cf-note textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-note textarea { min-height: 120px; resize: vertical; }
#cf-note :focus-visible { outline: 2px solid #4b5563; outline-offset: 2px; }
#cf-note button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #4b5563; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-note button:disabled { opacity: 0.6; cursor: wait; }
</style>
<form id="cf-note" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_subject" value="Note from the suggestion box">
  <label for="note-text">What would make this better?
    <textarea id="note-text" name="note" required></textarea>
  </label>
  <button type="submit">Drop it in the box</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

14. RSVP form

Preview

Can you come?

Yes or no first, details after. min and max on the guests field stop a typo of 40 when you meant 4. Every answer lands in one inbox, and the CSV export gives you the head count without retyping names from emails.

RSVP form: HTML
<style>
#cf-rsvp { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-rsvp label, #cf-rsvp legend { display: grid; gap: 6px; font-weight: 600; }
#cf-rsvp input, #cf-rsvp select, #cf-rsvp textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-rsvp textarea { min-height: 120px; resize: vertical; }
#cf-rsvp :focus-visible { outline: 2px solid #be185d; outline-offset: 2px; }
#cf-rsvp button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #be185d; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-rsvp button:disabled { opacity: 0.6; cursor: wait; }
#cf-rsvp fieldset { display: flex; gap: 18px; margin: 0; padding: 0; border: 0; }
#cf-rsvp fieldset label { display: flex; gap: 6px; align-items: center; font-weight: 400; }
</style>
<form id="cf-rsvp" action="https://formgong.com/submit" method="POST">
  <input type="hidden" name="access_key" value="fk_your_access_key">
  <input type="hidden" name="_subject" value="RSVP">
  <label for="rsvp-name">Your name
    <input id="rsvp-name" name="name" autocomplete="name" required>
  </label>
  <label for="rsvp-email">Email
    <input id="rsvp-email" type="email" name="email" autocomplete="email" required>
  </label>
  <fieldset>
    <legend>Can you come?</legend>
    <label><input type="radio" name="attending" value="Yes" required> Yes</label>
    <label><input type="radio" name="attending" value="No"> Sorry, no</label>
  </fieldset>
  <label for="rsvp-guests">Guests coming with you
    <input id="rsvp-guests" type="number" name="guests" min="0" max="4" value="0">
  </label>
  <label for="rsvp-diet">Food we should know about (optional)
    <input id="rsvp-diet" name="dietary">
  </label>
  <button type="submit">Send my answer</button>
  <div aria-hidden="true" style="position:absolute;inset-inline-start:0;top:0;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)">
    <label>Leave this field empty <input type="text" name="botcheck" tabindex="-1" autocomplete="off"></label>
  </div>
</form>

Which example should you start from?

  • A portfolio, a small business, a personal site: the simple form. Add the phone version only if you really call people back.
  • Several people answer messages: the subject dropdown, so the email subject tells you who should take it.
  • You sell a service: the quote request, or the booking request if the service happens at a set time.
  • A product or an app: the bug report and the rating form.
  • You need files or long answers: the file upload or the multi-step form.

None of them fits? The HTML form builder lets you add, reorder and remove fields one at a time, and the AI form generator writes a form from a sentence. For the code itself, line by line, see HTML contact form code.

Questions

Do these contact form examples work without a backend?

They need somewhere to send the data, and a static page cannot store it. Here that is Formgong: the action points to its /submit address. Replace fk_your_access_key with your free key and the form delivers. Without a real key nothing is delivered.

Can I change the fields?

Yes. Add or remove label and input pairs. Give every new field a name attribute, or its value is not sent. Formgong stores whatever named fields arrive.

Do I need JavaScript?

No for most of them. The feedback, newsletter and multi-step examples use a short script to stay on the page; without it they still post as a normal form, except that the multi-step one then shows both steps at once.

How do I stop spam?

Keep the hidden botcheck field. Formgong sends a submission to spam when that field has text. If bots still get through, turn on Cloudflare Turnstile in the form settings.

Can I send visitors to my own thank-you page?

Yes. Add a hidden input named _redirect with your page's address. It must be on a domain you added to the form in Formgong.

Sources

Checked on 16.10.2026 against these public pages: MDN: <form>, MDN: autocomplete, MDN: input type=file, CJEU, Planet49 (C-673/17), Formgong HTML guide.

← Formgong