All WordPress HTML Templates Forms & Webhooks AI & Tools
Forms & Webhooks

Surveys and Feedback Widgets That People Finish

How to design a feedback widget people actually finish: accessible NPS form markup, partial submissions, branched follow-ups, trigger rules and real code.

A real working moment illustrating the theme of an article about feedback widget. Wide 16:9 banner, one strong focal point, magazine editorial quality, authentic and unstaged.

A client came to us with a customer satisfaction survey that had been live for seven months. Fourteen questions, three of them matrix grids, one mandatory free-text box at position four. It had collected 61 complete responses out of roughly 4,900 opens. The product team had been making roadmap decisions on those 61 responses for over half a year.

We replaced it with a two-question widget. Same traffic, same audience, same month. Response volume went from single digits per week to a few hundred, and the free-text answers got longer, not shorter, because people who had already invested one click were willing to invest thirty seconds of typing.

That is the whole game. A feedback widget is not a form you happen to display on a page: it’s an interruption you have to earn, and every design decision either buys you goodwill or spends it. Here’s what we’ve learned shipping these across client sites, including the parts that go wrong.

Key Takeaways

  • Drop-off is roughly exponential per required question, not linear. Cutting a survey from ten questions to three typically multiplies completed responses several times over, and the responses you lose are the ones from people who were skimming anyway.
  • Ask the first question inline, in the widget, with no submit button. One click on a rating scale should immediately register a partial response, so a person who abandons at the follow-up still gave you usable data.
  • An NPS form is a trend line, not an insight. The 0 to 10 score tells you almost nothing on its own: the mandatory value is in the open-ended follow-up question, which should change wording based on the score given.
  • Build the scale from native radio inputs inside a fieldset with a legend. You get arrow-key navigation, screen reader grouping and form semantics for free, and you avoid the div-and-JavaScript rebuild that breaks in every accessibility audit.
  • Suppress aggressively: one ask per person per 90 days, never on first session, never during checkout, and store the dismissal in localStorage plus a server-side flag for logged-in users.

The completion math nobody shows you

Every required field is a decision point where a percentage of people leave. The losses compound. If each question costs you 12 percent of remaining respondents, a five question survey retains about 53 percent of starters and a twelve question survey retains about 21 percent. Those are made-up rates for illustration, but the shape is real and you can measure your own version in an afternoon by firing an analytics event on each question view.

What matters more than the count is the type of question. In our logs, the expensive ones are consistently: mandatory free text, matrix grids on mobile, anything asking for a number the person has to look up, and any question where none of the options apply. A ten-question survey of single-tap radio choices will out-complete a four-question survey with two open text boxes. Effort per question, not questions per survey.

There’s a second cost that never shows in the funnel: quality decay. Past roughly question six, straight-lining starts. People pick the same column down a grid to reach the end. You’ll see it as suspiciously low variance in the middle of your dataset. Those rows are worse than no data because they look legitimate in a spreadsheet.

Anatomy of a feedback widget people finish

The pattern we now build by default has four states and no modal overlay.

  1. Collapsed prompt. A small anchored panel, bottom right on desktop, bottom sheet on mobile. One line of copy and the first question visible. Not a button that opens a form. The question itself is the bait.
  2. First answer as commitment. Clicking a rating submits immediately over fetch. No submit button on step one.
  3. Follow-up. One open text field, autofocused, with a placeholder that is a real prompt rather than “Your feedback”. Skippable.
  4. Confirmation. Announce it in an aria-live region, auto-dismiss after four seconds, and never ask again for the cooldown period.

Do not put this in a centre-screen modal that blocks the page. We tested both on the same site. The modal collected more responses per impression and generated noticeably more support tickets containing the word “annoying”. If you are measuring only completion rate, the modal wins. If you are measuring anything else, it doesn’t.

The markup for the scale should stay boring:

<form id="nps-widget" class="nps" action="https://webforms.to/submit/YOUR_TOKEN" method="POST">
  <fieldset>
    <legend>How likely are you to recommend us to a colleague?</legend>
    <div class="nps__scale">
      <!-- Radios give free arrow-key navigation and a single tab stop for the whole group -->
      <label><input type="radio" name="score" value="0"><span>0</span></label>
      <label><input type="radio" name="score" value="1"><span>1</span></label>
      <!-- ... through to 10 -->
      <label><input type="radio" name="score" value="10"><span>10</span></label>
    </div>
    <p class="nps__anchors"><span>Not at all likely</span><span>Extremely likely</span></p>
  </fieldset>

  <div id="nps-followup" hidden>
    <label for="nps-reason"></label>
    <textarea id="nps-reason" name="reason" rows="3"></textarea>
    <button type="submit">Send</button>
  </div>

  <input type="hidden" name="page" value="">
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off" hidden>
  <p id="nps-status" role="status" aria-live="polite"></p>
</form>

Hide the radio input visually with a clip rectangle rather than display: none, which removes it from the accessibility tree in some combinations, and style the sibling span:

.nps__scale input {
  position: absolute;
  width: 1px; height: 1px;
  clip-path: inset(50%);          / keeps the control focusable and announced /
}
.nps__scale span {
  display: grid; place-items: center;
  min-width: 44px; min-height: 44px;  / touch target floor, do not shrink this /
  border: 1px solid var(--line);
  border-radius: .375rem;
  cursor: pointer;
}
.nps__scale input:focus-visible + span { outline: 2px solid currentColor; outline-offset: 2px; }
.nps__scale input:checked + span { background: var(--accent); color: #fff; }

@media (max-width: 30rem) {
  .nps__scale { display: grid; grid-template-columns: repeat(6, 1fr); gap: .25rem; }
}

Eleven 44 pixel targets do not fit across a 360 pixel viewport. Wrap to two rows. Do not switch to a dropdown on mobile: taps go up, completion goes down, and you lose the visual sense of a scale.

The NPS form, done properly

Net Promoter Score comes from Fred Reichheld’s 2003 Harvard Business Review article. Promoters score 9 to 10, passives 7 to 8, detractors 0 to 6, and the score is the promoter percentage minus the detractor percentage. That’s the whole method. It is a single number derived from a bucketed ordinal scale, which means it is statistically crude, sensitive to sample composition, and easy to game by asking only happy users.

We still ship NPS forms, for one reason: consistency. Because the question wording is fixed, a trend over eight quarters is meaningful even when the absolute number isn’t. The mistake is treating it as a diagnostic. It isn’t. The follow-up is.

Branch the follow-up copy on the score. Generic “Tell us why” gets generic answers:

const form = document.getElementById('nps-widget');
const followup = document.getElementById('nps-followup');
const reason = document.getElementById('nps-reason');
const label = followup.querySelector('label');
const status = document.getElementById('nps-status');

form.page.value = location.pathname;

form.addEventListener('change', async (e) => {
  if (e.target.name !== 'score') return;
  const score = Number(e.target.value);

  // Fire the partial straight away. If they never type a word, we still have the score.
  fetch(form.action, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ score, page: location.pathname, stage: 'partial' })
  }).catch(() => {});

  label.textContent = score <= 6
    ? 'What went wrong? Be specific, we read every one.'
    : score <= 8
      ? 'What would have made this a 10?'
      : 'What do you use it for most?';

  followup.hidden = false;
  reason.focus();
});

form.addEventListener('submit', async (e) => {
  e.preventDefault();
  await fetch(form.action, { method: 'POST', body: new FormData(form) });
  status.textContent = 'Thanks, that helps.';
  form.querySelector('fieldset').hidden = true;
  followup.hidden = true;
  localStorage.setItem('nps:asked', Date.now());
});

Two details worth defending. First, the partial POST means a person who rates 3 and closes the tab still counted, which typically recovers a meaningful share of otherwise lost detractor signal. Second, “What would have made this a 10?” is the highest-yield question we’ve ever put in a survey. It converts vague passives into specific feature requests.

Question wording that doesn’t bias the answer

Most bad survey design isn’t the widget. It’s the sentence.

  • Leading: “How helpful was our new dashboard?” presupposes helpful. Ask “How would you rate the new dashboard?” with a neutral scale.
  • Double-barrelled: “Was the checkout fast and easy?” Two questions, one answer, uninterpretable data. Split them or drop one.
  • Unbalanced scales: Excellent / Very good / Good / Fair. Three positives and one soft negative. Your data will read positive regardless of reality.
  • Absolute time recall: “How many times did you use X last month?” People guess. Offer ranges, or read it from your own analytics instead of asking.
  • Missing escape hatch: If “Not applicable” is a real state for some users, include it. Otherwise they pick something false or leave.

Skip the demographic block entirely unless you can name the decision it will change. Age brackets and company size collected “for segmentation” almost always end up unsegmented in a spreadsheet nobody opens.

Timing, triggers and the right to be left alone

Trigger on completed actions, not on elapsed time. Someone who has just finished an order, published a project, or read to the bottom of a documentation page has an opinion. Someone who has been idle for 30 seconds on a pricing page has a browser tab open behind their email client.

Our default rules on client builds:

  • Never in the first session. Never inside a checkout or signup flow.
  • Fire on a real event: order_complete, third login, scroll past 80 percent plus 45 seconds on page.
  • Dismissal is permanent for 90 days. A close button that reappears next page load is hostile and will get your widget blocked at the ad-block layer.
  • For logged-in users, mirror the suppression flag server-side. localStorage does not survive a device switch, and asking the same person on their phone that you asked yesterday on desktop reads as harassment.
  • Respect prefers-reduced-motion for the slide-in, and never animate over content the user is reading.

Exit-intent triggers deserve a specific warning: they don’t exist on touch devices in any reliable form, so you’re building a desktop-only feature and skewing your sample toward desktop users. If your traffic is 70 percent mobile, so is your blind spot.

Where the answers actually go

The plumbing kills more feedback programmes than the design does. A widget that emails responses to an inbox nobody owns is theatre.

Decide three things before you write the markup: who reads the responses, on what day of the week, and what happens to a detractor score within 24 hours. No answer to the third one means don’t launch an NPS form at all. Ask a passive question in your docs instead.

For static sites and Jamstack builds there’s no reason to stand up a backend for this. We route survey submissions through WebForms, which gives you a POST endpoint, delivery to email, Slack, Telegram or a webhook, and no server to patch. Detractor scores go to a Slack channel with an owner; everything else batches to a weekly digest. That routing rule matters more than any styling decision in this article.

Whatever you use, add a honeypot field and a minimum time-to-submit check. Survey endpoints get hit by the same bots that hit contact forms, and 200 junk rows will poison a small sample. The same hygiene we apply to newsletter signup forms applies here.

Measure the survey, not just the answers

Instrument the widget itself as a funnel: impression, first interaction, partial submit, complete submit, dismiss. Then watch four numbers.

  • Start rate (interactions per impression). Below about 5 percent, your placement or trigger is wrong, not your questions.
  • Partial to complete ratio. If most people rate and then abandon the follow-up, your follow-up copy is asking for work.
  • Median time to complete. Suspiciously fast completions on a long survey mean straight-lining.
  • Free-text length distribution. The single best proxy for whether people care. Rising median length is a healthier signal than a rising score.

If you’re rolling this out across a lot of pages in WordPress, build it once as a block pattern rather than pasting a script into a dozen templates. We covered that approach in our guide to reusable block pattern libraries, and it’s the difference between updating one file and hunting eleven.

Frequently Asked Questions

How many questions should a feedback widget have?

Two on the widget itself: a rating and one open follow-up. If you genuinely need more, ask the rating inline, then offer a link to a longer survey on a dedicated page for people who opt in. Mixing a fifteen question research survey into an anchored widget is the most common reason completion collapses.

Is NPS still worth using in 2026?

As a trend metric across quarters, yes, because the fixed wording makes historical comparison valid. As a diagnostic or a KPI tied to bonuses, no: it is a coarse bucketed score that is trivially skewed by who you sample and when. Use the score to notice change and the follow-up text to understand it.

Should the survey be anonymous?

Anonymous responses get more honest criticism; identified responses let you close the loop with the person who complained. Our default is identified for logged-in users with a visible note saying so, and anonymous for public marketing pages. Never quietly attach a user ID to something you presented as anonymous.

How do I stop the widget hurting Core Web Vitals?

Load the script with defer or on the trigger event rather than at page load, and reserve no layout space until it’s shown so it cannot cause a shift. Because it’s anchored and fixed-position, an animated slide-in from outside the viewport won’t count toward CLS. Keep the whole thing under about 5KB of JavaScript, which is achievable without a third-party SDK.

Can I collect useful feedback without a backend?

Yes. A static HTML widget that POSTs to a form API endpoint covers every case described here, including partial submissions and routing to Slack. You only need a backend if you want per-user suppression across devices or server-side response deduplication.

Pick one page with real intent behind it, put a single rating question and one branched follow-up on it, and route detractor responses to a named person’s Slack channel. Ship that this week. You’ll learn more from three hundred two-question responses than from another seven months of a fourteen-question survey that sixty-one people were patient enough to finish.

accessible nps form markup feedback widget feedback widget design in-app survey trigger rules nps follow-up question wording survey completion rate optimisation survey question bias mistakes website feedback widget without backend