Experience rating

Floating feedback card (bottom-right) with a 1–5 star rating. A comment box reveals for low scores, a brand Submit button appears once a rating is picked, and a success check plays on submit. Self-shows with a per-user cooldown and never touches host form state.

Preview
Click the button to trigger the card. Pick a rating; ratings ≤ 3 reveal a comment box. On submit the emitted { rating, comments, metadata } object is shown.

Click to open the feedback card.

Score-aware placeholder
Stars with commentThreshold=5 (comment always shows) and a commentPlaceholder function that flips the prompt by score — "Tell us what we did right" on high scores, the opposite on low ones.

Comments always show; the placeholder flips by score.

Installation
$Terminal
npx shadcn@latest add https://sdk-components.thesqd.com/r/experience-rating.json
Usage
Drop it anywhere in your tree. It portals to <body>, so it never affects host form state or progress.
TSImport
import { ExperienceRating } from "@/components/blocks/experience-rating";
TSExample
import { ExperienceRating } from "@/components/blocks/experience-rating";

export function Feedback() {
  return (
    <ExperienceRating
      // Anything here is passed straight through to onSubmit.
      metadata={{ userId: "abc", formId: "welcome", route: "/onboarding" }}
      onSubmit={async ({ rating, comments, metadata }) => {
        // Persist however the host app likes (its own route, Supabase, …).
        await fetch("/api/feedback", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ rating, comments, metadata }),
        });
      }}
    />
  );
}
Output
onSubmit receives (and the block emits) this shape.
{
  "rating": 3,
  "comments": "test",
  "metadata": {}
}
Composition
One block export plus the button-triggered demo.
ExperienceRating             — the floating feedback card (self-showing, cooldown-gated)
ExperienceRatingDemo         — button-triggered preview
ExperienceRatingScoredDemo   — score-aware placeholder preview
API Reference
Props for ExperienceRating.
PropTypeDefaultDescription
metadataRecord<string, any>{}Passed straight through to onSubmit and into the emitted object.
onSubmit(r: { rating, comments, metadata }) => void | PromiseCalled on submit. Host decides how to persist. Success check awaits the promise.
onDismiss() => voidFired when dismissed via the X.
openbooleanControlled visibility. When set, auto-show + cooldown are bypassed.
onOpenChange(open: boolean) => voidFires with the next open state.
ratingStyle"emoji" | "stars""emoji"Rating control style.
emojisstring[]["😞","🙁","😐","🙂","😄"]Override the 5 emojis (low→high). Only used when ratingStyle="emoji".
titleReactNode"How was your experience?"Card heading.
descriptionReactNode"Your feedback helps us improve."Sub-line under the title.
commentThresholdnumber3Show the comment box when the rating is ≤ this value. Set to 5 to always show.
commentPlaceholderstring | ((rating: number) => string)"Tell us what we could do better…"Comment placeholder. Pass a function of the rating to vary it by score, e.g. "Tell us what we did right" on 5.
submitLabelReactNode"Submit feedback"Submit button label.
successLabelReactNode"Thanks for the feedback!"Success-state message.
cooldownDaysnumber7Don't re-prompt within this many days of last shown/dismissed/filled.
storageKeystring"squad-experience-rating"localStorage namespace for the cooldown timestamps.
anchor"fixed" | "absolute""fixed""fixed" portals to <body>; "absolute" anchors to the nearest positioned parent.