# Feedback

> A score that changes the question: pick 0–10 or stars, the score rolls into the panel, and the follow-up and its tags fit what you picked.

Most feedback widgets ask for a number and then the same “Any comments?” whether you gave a 2 or a 10, so the answers say little. Here the question and its scale sit on a lit panel, and a band of light along its top follows the score you’re on. Pick one and the thumb is thrown to it while the number rolls into the chip beside the question. The follow-up opens under it and fits the score: “What went wrong?” for a 3, “What would make it a 10?” for a 7, “What do you love most?” for a 9, each with quick tags that come in from the side the score moved. Send goes Sending → Sent and the follow-up turns into the thanks with what you said. Where it goes is yours: onSubmit gets the score, its band, the tags and the comment.

## Install

```bash
npx shadcn@latest add https://hairlineui.com/r/feedback.json
```

Needs React 19, Tailwind CSS v4 and shadcn/ui theme variables (any style, light and dark). Icons from lucide-react.

- npm: `lucide-react`
- Hairline UI components it needs (the CLI installs them too): `number-roll`, `status-button`, `text-morph`, `textarea`
- Docs and live preview: https://hairlineui.com/ui/feedback
- Registry item (source): https://hairlineui.com/r/feedback.json

## Usage

```tsx
import { Feedback } from "@/components/feedback";

<Feedback
  question="How likely are you to recommend Relay to a friend?"
  onSubmit={(answer) => fetch("/api/feedback", { method: "POST", body: JSON.stringify(answer) })}
/>
```

## With your own endpoint

Post the answer to a route that stores it (a table, a sheet, your analytics). Word the follow-ups for your product with prompts.

```tsx
// app/api/feedback/route.ts
export async function POST(req: Request) {
  const { score, band, tags, comment } = await req.json();
  await db.feedback.insert({ score, band, tags, comment, at: new Date() });
  return Response.json({ ok: true });
}

// components/how-was-it.tsx
"use client";
import { Feedback } from "@/components/feedback";

export function HowWasIt() {
  return (
    <Feedback
      scale="stars"
      question="How was your order?"
      prompts={{
        low: { question: "What went wrong?", tags: ["Arrived late", "Damaged", "Wrong item", "Not as described"] },
        high: { question: "What did you like?", tags: ["Fast delivery", "Packaging", "Quality"] },
      }}
      onSubmit={async (answer) => {
        const res = await fetch("/api/feedback", { method: "POST", body: JSON.stringify(answer) });
        if (!res.ok) throw new Error("Couldn’t send that. Try again.");
      }}
    />
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `onSubmit` | `(answer: FeedbackAnswer) => Promise<unknown>` | – | Gets { score, band: "low" \| "mid" \| "high", tags, comment }; throw an Error to show its message. |
| `question` | `string` | `"How likely are you to recommend us to a friend?"` | The question on the panel. |
| `scale` | `"nps" \| "stars"` | `"nps"` | 0–10 (0–6 low, 7–8 mid, 9–10 high), or one to five stars (1–2 low, 3 mid, 4–5 high). |
| `prompts` | `Partial<Record<band, { question, tags }>>` | – | The follow-up question and quick tags for each band. |
| `ends` | `[string, string]` | `"Not likely", "Very likely" / "Poor", "Great"` | Words under each end of the scale. |
| `onDismiss` | `() => void` | – | Shows a close button on the panel. |

## States

- **ask**: The lit panel with the question, a “?” chip and the scale in an inset track; the ends are labelled under it.
- **hover**: With a mouse, the thumb previews the score you’re over at half strength and the chip rolls to it; leaving puts both back.
- **score**: The thumb is thrown to the score (460ms with a slight overshoot), the chip’s number rolls, and the light along the panel’s top glides over it (620ms). Stars fill one after another (40ms apart) and the word under them morphs.
- **follow-up**: The first score opens the follow-up (460ms); a score in another band clears the tags and brings the new question and tags in from the side the score moved, 35ms apart.
- **sending**: Send feedback → Sending; tags and the comment are locked.
- **thanks**: A check draws itself in the button, then the form turns into “Thanks, that helps.” with the score and tags, and the scale is locked.
- **error**: If onSubmit throws, the button shakes into Try again and your message shows under it; nothing is lost.

## What it handles

A survey widget is usually a number picker, a text box and a thank-you message with no link between the score and what you ask next. This block asks the right follow-up for the score and sends it anywhere you like.

- **Two scales**: scale="nps" for 0–10, or "stars" for one to five with a word for each (Poor to Great).
- **A question for the score**: Low, middle and high scores each get their own question and tags; set prompts to word them for your product.
- **The score you can feel**: The thumb is thrown to it, the number rolls into the chip and the light follows; hovering previews before you commit.
- **Keyboard first**: Arrow keys move the score and number keys jump straight to it; tags are toggle buttons.
- **Sent in place**: onSubmit gets { score, band, tags, comment }; Send goes Sending → Sent and the form turns into the thanks.
- **Anywhere it’s shown**: Compact enough for a card, a corner or an email landing page; onDismiss adds a close button.

## Replaces

an NPS scale, a star rating, follow-up questions, feedback tags, a comment box, a thank-you state

## Where it fits

- **NPS surveys**: The 0–10 question in the app or an email landing page, with a follow-up that fits.
- **After an order**: Stars for a delivery or a purchase, with tags for what went right or wrong.
- **In-app feedback**: “How was this?” after a feature, a report or an AI answer.
- **Support follow-ups**: Rating a resolved ticket, with the reason when it went badly.

## Notes

- The scale is a radio group named by the question: arrows and Home/End move it, number keys jump to a score, and each star is named (“4 stars, Good”).
- The chip and the light are decoration; the checked radio is the score. Tags are toggle buttons with aria-pressed.
- The thanks is a status region, so sending is announced once; an error is an alert under the button.
- Hover previews only follow a real mouse, so a tap on a phone never leaves a preview behind.
- Its light comes from your theme: the panel is --primary with a band of --primary-foreground that follows the score, the track an inset 1px edge. No drop shadows, glow or blur.
- Built from Textarea, Number roll, Status button and Text morph, which the CLI installs with it. No animation library; with reduced motion the score, stars and follow-up change in place.

License: MIT (https://hairlineui.com/license#components)
