NPS Survey Form
Net Promoter Score form with a 0-10 rating scale and open comment field. Measure customer loyalty in one form.
Don't have one yet? Sign up now and get one in less than 5 minutes.
Add your Formtorch endpoint
Paste your form ID and the code below updates instantly.
<section class="ft-nps-demo" aria-label="NPS survey form demo">
<div class="ft-window">
<div class="ft-window-bar" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</div>
<form id="nps-form" class="ft-form" accept-charset="UTF-8">
<div class="ft-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path
d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"
/>
<path
d="M12 2 15.09 8.26 22 9.27l-5 4.87L18.18 21 12 17.77 5.82 21 7 14.14l-5-4.87 6.91-1.01L12 2Z"
transform="translate(6 4) scale(0.5)"
/>
</svg>
</div>
<div class="ft-form-heading">
<h2>NPS Survey</h2>
<p>Help us improve with one quick question.</p>
</div>
<div class="ft-divider" aria-hidden="true"></div>
<input type="hidden" name="_formName" value="NPS Survey" />
<fieldset class="ft-field">
<legend>
How likely are you to recommend Formtorch to a friend or colleague?
</legend>
<div class="ft-scale" role="radiogroup" aria-label="Score, 0 to 10">
<label class="ft-score">
<input type="radio" name="score" value="0" required />
<span>0</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="1" />
<span>1</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="2" />
<span>2</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="3" />
<span>3</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="4" />
<span>4</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="5" />
<span>5</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="6" />
<span>6</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="7" />
<span>7</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="8" />
<span>8</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="9" />
<span>9</span>
</label>
<label class="ft-score">
<input type="radio" name="score" value="10" />
<span>10</span>
</label>
</div>
<div class="ft-scale-labels">
<span>Not likely</span>
<span>Extremely likely</span>
</div>
</fieldset>
<div class="ft-field">
<label for="ft-comment">
What's the main reason for your score? <em>(optional)</em>
</label>
<textarea
id="ft-comment"
name="comment"
rows="3"
placeholder="Share any feedback or suggestions..."
></textarea>
</div>
<button class="ft-submit" type="submit">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="m22 2-7 20-4-9-9-4Z" />
<path d="M22 2 11 13" />
</svg>
<span>Submit Feedback</span>
</button>
<p id="ft-status" class="ft-status" aria-live="polite"></p>
</form>
</div>
</section>
<style>
html,
body {
margin: 0;
padding: 0;
background: #05070d;
}
.ft-nps-demo {
position: relative;
box-sizing: border-box;
width: 100%;
min-height: 100vh;
padding: clamp(24px, 5vw, 64px);
display: grid;
place-items: center;
font-family:
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
color: #f8fafc;
background:
radial-gradient(circle at 22% 18%, rgb(37 99 235 / 0.2), transparent 32%),
radial-gradient(
circle at 78% 75%,
rgb(37 99 235 / 0.18),
transparent 30%
),
#05070d;
}
.ft-nps-demo,
.ft-nps-demo * {
box-sizing: border-box;
}
.ft-nps-demo::before,
.ft-nps-demo::after {
content: "";
position: absolute;
z-index: 0;
width: clamp(90px, 16vw, 130px);
height: clamp(130px, 20vw, 190px);
pointer-events: none;
opacity: 0.45;
background-image: radial-gradient(
rgb(37 99 235 / 0.9) 1.5px,
transparent 1.5px
);
background-size: 20px 20px;
}
.ft-nps-demo::before {
top: 9%;
left: 5%;
}
.ft-nps-demo::after {
bottom: 9%;
right: 5%;
}
.ft-window {
position: relative;
z-index: 1;
width: min(100%, 560px);
overflow: hidden;
border: 1px solid rgb(148 163 184 / 0.22);
border-radius: 24px;
background:
linear-gradient(180deg, rgb(255 255 255 / 0.06), transparent 130px),
linear-gradient(145deg, rgb(15 23 42 / 0.96), rgb(2 6 23 / 0.96));
box-shadow:
0 26px 80px rgb(0 0 0 / 0.45),
0 0 0 1px rgb(255 255 255 / 0.04),
0 0 44px rgb(37 99 235 / 0.2);
}
.ft-window-bar {
position: relative;
z-index: 1;
height: 64px;
display: flex;
align-items: center;
gap: 12px;
padding: 0 32px;
border-bottom: 1px solid rgb(148 163 184 / 0.12);
background: rgb(255 255 255 / 0.025);
}
.ft-window-bar span {
width: 14px;
height: 14px;
border-radius: 999px;
background: #2563eb;
box-shadow: 0 0 20px rgb(37 99 235 / 0.55);
}
.ft-window-bar span:nth-child(2) {
background: #1d4ed8;
}
.ft-window-bar span:nth-child(3) {
background: #334155;
box-shadow: none;
}
.ft-form {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
gap: 22px;
padding: clamp(32px, 6vw, 44px);
text-align: left;
}
.ft-icon {
align-self: center;
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
border-radius: 16px;
background: linear-gradient(
160deg,
rgb(37 99 235 / 0.22),
rgb(15 23 42 / 0.4)
);
border: 1px solid rgb(148 163 184 / 0.18);
box-shadow: 0 0 30px rgb(37 99 235 / 0.18);
}
.ft-icon svg {
width: 26px;
height: 26px;
color: #3b82f6;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.ft-form-heading {
text-align: center;
}
.ft-form-heading h2 {
margin: 0;
color: #f8fafc;
font-size: clamp(24px, 5vw, 30px);
line-height: 1.15;
letter-spacing: -0.03em;
}
.ft-form-heading p {
margin: 10px auto 0;
color: #94a3b8;
font-size: clamp(14px, 2.4vw, 15px);
line-height: 1.55;
max-width: 320px;
}
.ft-divider {
height: 1px;
background: rgb(148 163 184 / 0.14);
}
.ft-field {
width: 100%;
margin: 0;
padding: 0;
border: 0;
}
.ft-field legend {
display: block;
width: 100%;
margin: 0 0 16px;
padding: 0;
color: #e2e8f0;
font-size: 15px;
font-weight: 600;
line-height: 1.4;
}
.ft-field label {
display: block;
margin-bottom: 10px;
color: #e2e8f0;
font-size: 14px;
font-weight: 600;
}
.ft-field label em {
color: #64748b;
font-style: normal;
font-weight: 500;
}
.ft-scale {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.ft-score {
position: relative;
flex: 1 1 40px;
}
.ft-score input {
position: absolute;
inset: 0;
margin: 0;
opacity: 0;
cursor: pointer;
}
.ft-score span {
display: flex;
height: 52px;
align-items: center;
justify-content: center;
border: 1px solid rgb(148 163 184 / 0.24);
border-radius: 10px;
background: rgb(15 23 42 / 0.5);
color: #cbd5e1;
font-size: 15px;
font-weight: 600;
pointer-events: none;
transition:
border-color 160ms ease,
background-color 160ms ease,
color 160ms ease,
box-shadow 160ms ease;
}
.ft-score:hover span {
border-color: rgb(148 163 184 / 0.4);
}
.ft-score input:checked ~ span {
border-color: #2563eb;
background: #2563eb;
color: #fff;
box-shadow: 0 8px 20px rgb(37 99 235 / 0.35);
}
.ft-score input:focus-visible ~ span {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
.ft-scale-labels {
display: flex;
justify-content: space-between;
margin-top: 10px;
color: #64748b;
font-size: 12px;
}
.ft-field textarea {
width: 100%;
resize: vertical;
border: 1px solid rgb(148 163 184 / 0.24);
border-radius: 10px;
background: rgb(15 23 42 / 0.52);
color: #f8fafc;
font: inherit;
font-size: 15px;
padding: 14px 16px;
outline: none;
transition:
border-color 160ms ease,
background-color 160ms ease,
box-shadow 160ms ease;
}
.ft-field textarea::placeholder {
color: #64748b;
}
.ft-field textarea:hover {
border-color: rgb(148 163 184 / 0.38);
}
.ft-field textarea:focus-visible {
border-color: #2563eb;
background: rgb(15 23 42 / 0.78);
box-shadow: 0 0 0 4px rgb(37 99 235 / 0.18);
}
.ft-submit {
appearance: none;
width: 100%;
height: 56px;
border: 0;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 0 22px;
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: #fff;
font: inherit;
font-size: 16px;
font-weight: 650;
cursor: pointer;
box-shadow:
inset 0 1px 0 rgb(255 255 255 / 0.2),
0 16px 34px rgb(37 99 235 / 0.18);
transition:
transform 160ms ease,
filter 160ms ease,
box-shadow 160ms ease;
}
.ft-submit svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.ft-submit:hover {
filter: brightness(1.05);
box-shadow:
inset 0 1px 0 rgb(255 255 255 / 0.22),
0 20px 42px rgb(37 99 235 / 0.24);
}
.ft-submit:active {
transform: translateY(1px);
}
.ft-submit:focus-visible {
outline: none;
box-shadow:
0 0 0 4px rgb(37 99 235 / 0.22),
0 20px 42px rgb(37 99 235 / 0.24);
}
.ft-submit:disabled {
cursor: not-allowed;
opacity: 0.65;
filter: grayscale(0.15);
}
.ft-status {
min-height: 24px;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: #94a3b8;
font-size: 14px;
line-height: 1.4;
}
.ft-status svg {
width: 17px;
height: 17px;
flex: 0 0 auto;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.ft-status[data-state="success"] svg {
color: #34d399;
}
.ft-status[data-state="error"] {
color: #f87171;
}
@media (max-width: 480px) {
.ft-nps-demo {
padding: 18px;
}
.ft-nps-demo::before,
.ft-nps-demo::after {
display: none;
}
.ft-window {
border-radius: 20px;
}
.ft-window-bar {
height: 54px;
padding: 0 22px;
}
.ft-form {
gap: 18px;
padding: 28px 22px;
}
.ft-scale {
gap: 6px;
}
.ft-score span {
height: 42px;
font-size: 13px;
}
.ft-submit {
height: 52px;
}
}
</style>
<script>
const form = document.getElementById("nps-form");
const status = document.getElementById("ft-status");
const button = form.querySelector("button[type='submit']");
const buttonLabel = button.querySelector("span");
const successIcon =
'<svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><path d="m9 12 2 2 4-4"></path></svg>';
const errorIcon =
'<svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><path d="m15 9-6 6"></path><path d="m9 9 6 6"></path></svg>';
form.addEventListener("submit", async (event) => {
event.preventDefault();
status.innerHTML = "";
status.removeAttribute("data-state");
button.disabled = true;
buttonLabel.textContent = "Sending...";
try {
const response = await fetch("https://formtorch.com/f/abc123", {
method: "POST",
headers: {
"X-Requested-With": "XMLHttpRequest",
Accept: "application/json",
},
body: new FormData(form),
});
if (!response.ok) {
throw new Error("Submission failed");
}
form.reset();
status.dataset.state = "success";
status.innerHTML = successIcon + "<span>Response recorded</span>";
buttonLabel.textContent = "Submit Feedback";
} catch {
status.dataset.state = "error";
status.innerHTML =
errorIcon + "<span>Something went wrong. Please try again.</span>";
buttonLabel.textContent = "Try again";
} finally {
button.disabled = false;
}
});
</script>
Why Use This Template
What is an NPS survey form?
An NPS (Net Promoter Score) survey form asks one question on a 0 to 10 scale: how likely are you to recommend this to a friend or colleague, plus an optional comment field. It's one of the most widely used ways to measure customer loyalty in a single form.
Why do you need an NPS survey?
NPS gives you a simple, repeatable number to track over time, and the comment field tells you why customers gave that score. It's a faster way to catch dissatisfaction than waiting for a support ticket or a customer who quietly leaves.
How do you add an NPS survey form with a backend like Formtorch?
Copy the HTML, React, Next.js, or Astro snippet above and connect it to your Formtorch endpoint: the scale and comment field are ready to submit. Every score and comment is stored in your dashboard, and you can export to CSV to calculate your NPS.
Related read: why building your own survey backend usually costs more than it saves
How it works
Create a Formtorch endpoint
Sign up and create a new form endpoint in the dashboard. You will get a unique URL in seconds.
Create endpointPaste it into your form
Set your form's action attribute to your endpoint URL. No other changes required.
Deploy and start receiving submissions
Every submission is stored in your dashboard, triggers email notifications, and can fire webhooks to any service.
Frequently asked questions
No. Formtorch works with standard HTML forms via the action attribute. No JavaScript required for basic submissions.
Start in less than 5 minutes
Create your endpoint, paste it into any template, and you are live.