Helpful Feedback Widget — Was This Helpful? UI

Helpful Feedback Widget · Cards · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

One-tap primary vote
A single yes/no question captures the highest-value signal first, with no barrier to participating.
Answer-aware follow-up
The follow-up question and reason tags adapt to a positive or negative vote, so the prompt always fits.
Multi-select reason tags
Toggle-selectable chips give one-tap structured categorisation that you can count across articles.
Optional free-text comment
An explicitly optional textarea captures depth from the minority who want to elaborate.
Skip-friendly flow
Both Send and Skip reach the thank-you, respecting users who only want to give the one-tap vote.
Sentiment-aware thank-you
The closing message differs for positive vs negative votes, closing the loop honestly.
Three-state card
Ask, follow-up, and thanks are one compact card toggled cleanly — no layout jumps.
Analytics-ready payload
finish() exposes { vote, tags, comment } ready to send to your feedback endpoint per article.

About this UI Snippet

Helpful Feedback Widget — Yes/No Vote with Contextual Follow-Up

Screenshot of the Helpful Feedback Widget snippet rendered live

The "Was this helpful? 👍 👎" widget at the bottom of documentation and help-center articles is the simplest, highest-response-rate way to learn whether your content actually works. This snippet builds the complete pattern in plain HTML, CSS, and vanilla JavaScript: a one-tap yes/no vote, a contextual follow-up that adapts to the answer, quick-select reason tags, an optional comment, and a thank-you state — three screens in one small card.

One tap to participate, more if they want

The barrier to feedback has to be near zero, so the first screen asks one question with two buttons. That single tap is the most valuable signal — a raw helpful/unhelpful ratio per article — and many users will stop there, which is fine. Only after they've voted does the widget ask for more, so you capture the easy signal first and treat the detailed follow-up as a bonus rather than a gate.

A follow-up that adapts to the answer

The follow-up question and its reason tags change based on the vote. A thumbs-up asks "What did you like?" with positive tags (Clear and easy, Solved my problem, Well written); a thumbs-down asks "What could be better?" with diagnostic tags (Hard to follow, Missing information, Out of date). This matters because the useful question is different depending on the answer — asking an unhappy reader "what did you like?" wastes the moment, and asking a happy one "what's wrong?" is confusing. Matching the prompt to the sentiment gets you actionable, categorised feedback instead of vague free text.

Quick tags plus optional comment

Most people won't write a paragraph, but they'll happily tap a tag or two. The reason chips give one-tap structured categorisation (toggle-selectable, so multiple can apply), while the textarea stays explicitly optional for the minority who want to elaborate. This combination — structured tags for volume, free text for depth — is what makes the feedback both quantifiable (you can count "Out of date" across articles) and rich (you can read the specifics).

A real thank-you, sentiment-aware

Both the Send and Skip buttons lead to a thank-you state, because acknowledging the input matters even when the user skips the details. The message adapts too: a positive vote gets "glad it helped," a negative one gets "we'll use this to improve" — closing the loop honestly. Crucially, both paths end the interaction; the widget never re-asks, respecting that the user has already given what they chose to.

Lightweight and analytics-ready

The whole flow is three states toggled with the hidden attribute and driven by a few event listeners. The finish() function is where you'd send { vote, selectedTags, comment } to your analytics or feedback endpoint — the vote alone powers a per-article helpfulness score, while the tags and comment feed content-improvement decisions. Because it's so small and dependency-free, it drops into any docs page, blog post, or knowledge-base article without weighing the page down.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to trace the state transitions by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the TAGS object keys the reason chips to the up/down vote, or why both the Send and Skip buttons call the same finish function instead of diverging. The same assistant is useful for optimizing it — ask whether the selectedTags array's indexOf-based toggle logic would still be correct and fast if the tag list grew to dozens of entries. It's just as handy for extending the widget: ask it to persist a per-article vote in localStorage so a user can't vote twice, add a star rating step before the yes/no question, or wire the finish function up to a real analytics call with the article id included. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "Was this helpful?" feedback widget in plain HTML, CSS, and JavaScript — no library, no framework.

Requirements:
- A card with three mutually exclusive states, toggled with the hidden attribute rather than being separate pages: an initial ask state with a question and Yes/No buttons, a follow-up state, and a thank-you state.
- Define two separate arrays of short reason tags, one for a positive vote and one for a negative vote, keyed in a single object by vote value.
- Clicking Yes or No must hide the ask state, show the follow-up state, set the follow-up's heading text differently depending on which button was clicked, and render that vote's reason tags as toggle buttons generated from the corresponding array.
- Reason tag buttons must be multi-select: clicking one toggles a visual "on" class and adds or removes that tag's text from a running selected-tags array (using indexOf to check membership before pushing or splicing it out).
- Include an optional textarea for freeform comment text in the follow-up state.
- Both a Send button and a Skip button must lead to the same thank-you state, but the thank-you message text must differ depending on whether the original vote was positive or negative.
- Use event delegation (a single click listener on each container, using closest to find the relevant button) rather than binding one listener per generated tag button.
- Leave a clear point in the code (in the function that shows the thank-you state) where the vote, selected tags, and comment would be sent to a backend or analytics endpoint.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA "Was this article helpful?" card renders with Yes and No buttons.
  2. 2
    VoteClick Yes or No — the widget reveals a follow-up whose question and tags match your answer.
  3. 3
    Pick reason tagsTap one or more reason chips (multi-select) and optionally add a comment.
  4. 4
    Send or skipBoth Send and Skip lead to a sentiment-aware thank-you — the widget never re-asks.
  5. 5
    Edit the tagsChange the positive/negative tag lists in the TAGS object to match what you want to learn.
  6. 6
    Connect your analyticsIn finish(), send { vote, selectedTags, comment } to your feedback or analytics endpoint, keyed by article id.

Real-world uses

Common Use Cases

Documentation and help centers
The classic per-article helpfulness widget — pair with a FAQ search accordion for the broader help page.
Blog and knowledge-base posts
Measure which articles actually help readers and which need rewriting.
Product in-app guides
Collect feedback on tooltips, onboarding steps, or feature explainers in context.
Support and ticket resolution
Ask "did this solve your issue?" after a help response, pairing with a feedback tab widget for open-ended input.
Course and lesson pages
Gauge whether each lesson landed and gather specific improvement tags.
Learning multi-state micro-surveys
A reference for a low-friction vote-then-detail flow — compare with an NPS survey for the score-based variant.
Related: Hand-Drawn Annotation Card
See the Hand-Drawn Annotation Card for a related cards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

In the finish() function, send { vote, selectedTags, comment } along with the article id and URL to your analytics tool (a custom event) or a feedback endpoint. The vote alone gives you a per-article helpfulness ratio; the tags let you aggregate common issues ("Out of date" across docs), and the comment captures specifics worth reading.

Lowering the barrier to the first action maximizes response rate — most users will tap yes/no but not write a comment, and that single tap is the highest-value, most aggregatable signal. Showing the detailed follow-up only after the vote treats it as an optional bonus, so you never lose the easy signal by gating it behind effort.

After finish(), store a flag in localStorage keyed by article id (and optionally a timestamp), and check it on load — if present, render the thank-you state (or hide the widget) instead of the ask. For logged-in users, record the vote server-side against their account so it persists across devices.

Yes — the useful follow-up question depends on the answer. Asking an unhappy reader "what did you like?" wastes the moment, and asking a happy one "what went wrong?" is confusing. Matching the prompt and the reason tags to the sentiment produces actionable, correctly categorized feedback instead of mismatched noise.

In React, hold the vote, selected tags, and step in useState and conditionally render ask/follow-up/thanks; in Vue, use ref()/reactive() with v-if; in Angular, use component fields with *ngIf. The TAGS data and the finish() payload port unchanged — only the three-state toggling moves into the framework's conditional rendering.