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.
Related: Contact form generator · HTML contact template
<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
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.
Related: HTML form generator (phone and subject sample)
<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.
Related: Tailwind form generator (topic dropdown)
<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.
Related: Renovation estimate template · AI form generator
<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.
Related: Booking form generator · Salon booking template
<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
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.
Related: Support request template
<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
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.
Related: Customer feedback template
<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>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.
Related: Contact form with file upload: full guide
<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
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.
Related: HTML form builder
<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>11. Contact form with GDPR consent
Preview
Both boxes start unticked. If you rely on consent, a pre-ticked box does not count: the EU Court of Justice said so in the Planet49 case in 2019. Marketing gets its own box because it is a separate purpose, and it stays optional. Change "Example Studio" and the privacy link to yours. The ticked box arrives with the message as consent: yes, which is your record of it. Formgong stores submission data in the EU.
Related: GDPR and contact forms · GDPR form backend
<style>
#cf-gdpr { position: relative; display: grid; gap: 14px; max-width: 32rem; font: 16px/1.5 system-ui, sans-serif; color: #1f2933; }
#cf-gdpr label, #cf-gdpr legend { display: grid; gap: 6px; font-weight: 600; }
#cf-gdpr input, #cf-gdpr select, #cf-gdpr textarea { font: inherit; padding: 10px 12px; border: 1px solid #9aa5b1; border-radius: 8px; background: #fff; color: inherit; }
#cf-gdpr textarea { min-height: 120px; resize: vertical; }
#cf-gdpr :focus-visible { outline: 2px solid #2f5bea; outline-offset: 2px; }
#cf-gdpr button { justify-self: start; padding: 12px 20px; border: 0; border-radius: 8px; background: #2f5bea; color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
#cf-gdpr button:disabled { opacity: 0.6; cursor: wait; }
#cf-gdpr .cf-check { display: flex; gap: 10px; align-items: flex-start; font-weight: 400; }
#cf-gdpr .cf-check input { margin-top: 4px; }
</style>
<form id="cf-gdpr" action="https://formgong.com/submit" method="POST">
<input type="hidden" name="access_key" value="fk_your_access_key">
<label for="gdpr-name">Name
<input id="gdpr-name" name="name" autocomplete="name" required>
</label>
<label for="gdpr-email">Email
<input id="gdpr-email" type="email" name="email" autocomplete="email" required>
</label>
<label for="gdpr-message">Message
<textarea id="gdpr-message" name="message" required></textarea>
</label>
<label class="cf-check" for="gdpr-consent">
<input id="gdpr-consent" type="checkbox" name="consent" value="yes" required>
<span>I agree that Example Studio stores my message to answer it. <a href="/privacy">Privacy policy</a></span>
</label>
<label class="cf-check" for="gdpr-news">
<input id="gdpr-news" type="checkbox" name="newsletter" value="yes">
<span>Also send me occasional news (optional)</span>
</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>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.
Related: Dark contact template
<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.
Related: Minimal contact template
<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
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.
Related: RSVP template · Event registration template
<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.