Skip to content
WANCRAFT

F.145Forms & Inputs

Feedback Scale

A five-step satisfaction scale with drawn faces that morph as you hover, a follow-up comment and a thank-you state.

NewReactMotionSVGTailwindintermediate

Overview

Feedback Scale asks one question with five hand-drawn SVG faces, from very unhappy to very happy, coloured from a warm red to a positive green. A large face beside the question morphs towards whichever step you hover or choose: the mouth curve, eye squint, worried brows and cheeks are all derived from a single mood value animated on a spring, so expressions blend instead of swapping. Choosing a face reveals an optional comment field and the submit button; sending switches to a thank-you state with a little bounce and a drawn check, with an option to edit the answer. Underneath it is a real radiogroup of native radios, so arrow keys, focus and form submission work as expected. No emoji — every face is vector paths.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/feedback-scale.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/feedback-scale.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/feedback-scale.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { FeedbackScale } from "@/components/ui/feedback-scale";

export function FeedbackScaleExample() {
  return (
    <FeedbackScale onSubmit={(feedback) => console.log(feedback)} />
  );
}

03Props

PropTypeDefaultDescription
valuenumber | null—Controlled score, 1–5 (null for none).
defaultValuenumber | nullnullInitial score when uncontrolled.
onChange(score: number) => void—Called when a face is chosen.
onSubmit({ score, label, comment }) => void—Called when the answer is sent; the component then shows its thank-you state.
questionstring"How was your experience?"The question and the radiogroup's accessible name.
labels[string, string, string, string, string]["Very unhappy", …, "Very happy"]One label per step, most negative first.
showLabelsbooleantrueShow each step's label under its face.
showPreviewbooleantrueLarge face beside the question that morphs towards the hovered or chosen step.
showCommentbooleantrueReveal an optional comment field after a score is chosen.
commentLabelstring"Anything we could do better?"Label of the comment field.
submitLabelstring"Send feedback"Text of the submit button.
thanksMessagestring"Thanks — every response is read by the team."Message on the thank-you state.
size"sm" | "md" | "lg""md"Face size.
spring{ stiffness, damping, mass }{ stiffness: 260, damping: 22, mass: 0.8 }Spring of the face morph.
accentstring"#FF7448"Focus rings and the submit button (dark text sits on it, so keep it light).
namestring—Name for the radios (and `${name}-comment` for the textarea) inside a surrounding form.

04Features

  • 01Five vector faces — no emoji — coloured from warm red to green
  • 02Hero face morphs mouth, eyes, brows and cheeks from one spring-animated mood value
  • 03Hover previews, selection scales the face and slides an underline between steps
  • 04Optional comment field revealed after a choice
  • 05Thank-you state with a bounce and a drawn check, plus Edit response
  • 06Controlled or uncontrolled; onChange and onSubmit callbacks
motion · springmotion · layoutinput · hoverinput · clickinput · keyboardinput · focus

05Accessibility

  • Native radio inputs inside role="radiogroup" labelled by the question: one tab stop, arrow keys move and select.
  • Each radio is named with its label and position, e.g. "Happy, 4 of 5"; the faces themselves are aria-hidden.
  • The comment textarea has a visible label; the submit button only appears once a score exists.
  • After sending, focus moves to the "Feedback sent" heading inside a status region; Edit response returns focus to the chosen face.
  • Visible focus rings on every step, the textarea and the buttons.
  • With prefers-reduced-motion the face changes expression instantly (no morph) and the reveal and thank-you appear without movement.