webdevpuneet.com

Feedback Modal with Star Rating and Comment — Free HTML CSS JS Snippet

Feedback Modal with Star Rating and Comment · Modals · Plain HTML, CSS & JS · Live preview

CategoryModals

Copy & paste

Source Code

<div class="rfm-page"><button type="button" class="rfm-open" id="rfmOpen">Rate your experience</button></div>

<div class="rfm-backdrop" id="rfmBackdrop"></div>
<div class="rfm-modal" id="rfmModal" role="dialog" aria-modal="true" aria-labelledby="rfmTitle">
  <button type="button" class="rfm-close" id="rfmClose" aria-label="Close">✕</button>

  <div class="rfm-body" id="rfmBody">
    <h3 id="rfmTitle">How was your experience?</h3>
    <p class="rfm-sub">Your feedback helps us improve. This takes less than a minute.</p>

    <div class="rfm-stars" id="rfmStars" role="radiogroup" aria-label="Star rating">
      <button type="button" class="rfm-star" data-value="1" role="radio" aria-checked="false" aria-label="1 star">★</button>
      <button type="button" class="rfm-star" data-value="2" role="radio" aria-checked="false" aria-label="2 stars">★</button>
      <button type="button" class="rfm-star" data-value="3" role="radio" aria-checked="false" aria-label="3 stars">★</button>
      <button type="button" class="rfm-star" data-value="4" role="radio" aria-checked="false" aria-label="4 stars">★</button>
      <button type="button" class="rfm-star" data-value="5" role="radio" aria-checked="false" aria-label="5 stars">★</button>
    </div>
    <p class="rfm-rating-label" id="rfmRatingLabel">&nbsp;</p>

    <label class="rfm-comment-label" for="rfmComment">Tell us more <span>(optional)</span></label>
    <textarea id="rfmComment" class="rfm-comment" rows="3" maxlength="280" placeholder="What worked well? What could be better?"></textarea>
    <span class="rfm-counter" id="rfmCounter">280 left</span>

    <button type="button" class="rfm-submit" id="rfmSubmit" disabled>Submit feedback</button>
  </div>

  <div class="rfm-thanks" id="rfmThanks" hidden>
    <div class="rfm-check">
      <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>
    </div>
    <h3>Thanks for the feedback!</h3>
    <p id="rfmThanksSub">We appreciate you taking the time.</p>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Five-star rating built from role="radio" buttons inside a role="radiogroup"
Separate hover-preview state and committed-rating state that never overwrite each other
Comment field is genuinely optional — Submit only requires a star rating, not text
Live character counter computed directly from textarea.value.length, no drift possible
Counter turns red only in the final 20 characters as a real running-low warning
Thank-you message text adapts based on whether the rating was 4-5 stars or lower
Full form reset deferred until after the close transition finishes, avoiding a visible flash
Escape key, backdrop click, and a close button all dismiss the modal
Export as HTML file, React JSX, or React + Tailwind CSS
Mobile (375px), Tablet (768px), Desktop device preview buttons

About this UI Snippet

Star Rating Feedback Modal — Hover Preview, Optional Comment, Rating-Aware Confirmation

Screenshot of the Feedback Modal with Star Rating and Comment snippet rendered live

A feedback prompt that's just a comment box gets low response rates — typing takes effort. This modal leads with the lowest-effort input possible (tapping a star) and treats the comment field as genuinely optional, so a visitor can leave useful signal in one click while still being invited to add more.

Stars as buttons, not radio inputs

Each star is a real <button> with role="radio" inside a role="radiogroup" container — a semantic five-option single-choice control, but built without native <input type="radio"> elements so the star glyphs themselves can be styled and animated directly rather than styled via a hidden-input-plus-label workaround.

Hover preview vs. committed rating are two different states

paintStars(value) is called both from mouseenter (to preview what clicking would select) and from mouseleave (to restore whatever the actual currentRating currently is). This is why hovering star 4 lights up four stars temporarily, but moving the mouse away without clicking reverts back to whatever was last actually chosen — the hover state never overwrites currentRating itself, only the visual paint.

Comment is genuinely optional — the label says so and the logic enforces it

updateSubmitState() only checks currentRating === 0 — the comment textarea's content plays no role in whether Submit is enabled. The label text itself says "(optional)" and the code backs that claim up, rather than silently requiring text anyway (a common trust-eroding pattern in feedback forms that claim optionality but don't enforce it).

A character counter that's just arithmetic on the real value

MAX_LEN - comment.value.length recalculates on every input event — there's no separate tracked character count that could drift from the textarea's actual content, and the counter turns red only in the last 20 characters as a genuine "running low" warning rather than being red by default.

The thank-you message reads the rating, not just a generic "thanks"

Before revealing #rfmThanks, the submit handler checks currentRating >= 4 and picks between an appreciative message and a more empathetic "this goes straight to the product team" message for lower ratings — a small but meaningful difference from a single static thank-you that would read as tone-deaf after a 1-star rating.

Full reset happens after the transition, not before

After showing the thank-you state for 1.6 seconds, the modal closes, and only *then* — after another 250ms matching the CSS close transition — does the code reset the rating, comment, and counter back to their initial state. Resetting immediately on close would risk the visitor briefly seeing the form flash back to empty while the modal is still visually closing.

Customizing it

Swap the five hardcoded labels (Poor through Excellent) for wording that matches your product's tone, adjust MAX_LEN for a longer or shorter comment allowance, or wire the submitBtn click handler's current setTimeout-based demo flow to a real fetch() call that posts { rating: currentRating, comment: comment.value } to your feedback endpoint.

Step by step

How to Use

  1. 1
    Open the modalClick "Rate your experience" to open the feedback dialog.
  2. 2
    Hover over the starsA live preview lights up stars under your cursor before you click.
  3. 3
    Click a star to commit your ratingThe Submit button enables only once a rating is chosen — the comment stays optional.
  4. 4
    Optionally add a commentThe character counter updates live and turns red in the final 20 characters.
  5. 5
    Submit and see the thank-you stateThe message adapts based on whether the rating was 4-5 stars or lower.
  6. 6
    Wire up a real endpointReplace the submit handler's setTimeout demo flow with a fetch() call posting the rating and comment.

Real-world uses

Common Use Cases

Post-purchase and post-support feedback
Prompt for a quick rating right after a checkout or support interaction while the experience is fresh.
In-app NPS and satisfaction surveys
Pair with an NPS survey widget for a broader satisfaction-tracking program.
Feature and beta feedback collection
Trigger this modal after a visitor tries a new feature to capture reaction while it's top of mind.
Learn hover-vs-committed UI state
Study how paintStars() serves two different callers (hover preview and click commit) without either overwriting the other's intent.
App store and marketplace review prompts
Reuse the star-plus-optional-comment pattern before deep-linking a high rating out to a public review page.
Related: Helpful Feedback Widget
Pair with the Helpful Feedback Widget for a lighter-weight, inline alternative to this modal.

Got questions?

Frequently Asked Questions

Each star is a real <button> with role="radio" inside a role="radiogroup" wrapper, giving the same semantic single-choice meaning as native radio inputs while allowing the star glyph itself to be styled and scaled directly on hover and click, without the usual hidden-input-plus-styled-label workaround radio buttons typically need for custom styling.

paintStars(value) only updates which stars visually appear filled — it never touches the currentRating variable. Hovering calls paintStars() with the hovered star's value; moving the mouse away calls paintStars(currentRating), repainting back to whatever was last actually clicked. Only a click updates currentRating itself.

No. updateSubmitState() disables the Submit button based solely on whether currentRating is still 0 — the comment textarea's content is never checked. The label explicitly says "(optional)" and the enabling logic honors that rather than silently requiring text anyway.

It recalculates MAX_LEN - comment.value.length directly from the textarea's live value on every input event — there is no separately tracked counter variable that could fall out of sync with what is actually typed. It only switches to red text once 20 or fewer characters remain.

The submit handler checks whether currentRating is 4 or 5 and picks between an appreciative message and a more empathetic message acknowledging the feedback will reach the product team for lower ratings. A single generic "thanks!" message would feel tone-deaf immediately after someone leaves a 1-star rating.

The reset (clearing the rating, comment, and counter) is deferred by roughly 250ms after the close animation starts — matching the CSS transition duration — so the visitor never sees the form flash back to its empty starting state while the modal is still visibly animating closed.

Design your website