shadcn.io is not affiliated with official shadcn/ui
Feedback Rating Dialog Block for React Aria
A help desk's ended chat with a one-tap satisfaction dialog shown open: five drawn faces from very bad to great that record the score at once, then an optional comment with a question that fits the score.
Installation
Overview
Ask how it went in one tap, with this satisfaction dialog for React with React Aria. It opens from a closed support chat in Helpwell, a help desk: the header holds an outline Rate this chat Button, and the chat itself, a log of bubbles ending in "Chat closed", is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The Dialog holds five drawn faces in a single-choice Toggle Group, each a circle with two eyes and a mouth that follows the score, named from Very bad to Great. Pressing a face records the score at once and says "Recorded: Good" in a polite line; the face stays pressed. An optional Textarea asks the question that fits the score, what went wrong, what would have helped or what worked well. The only Shine Button reads Done, or Add comment once something is typed, and the drawn chat's closing row says what was recorded. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog, with the same faces and the same state.
FAQ
Why is the dialog already open, and what do I keep?
So you can see it without a click. On load the dialog is a copy drawn in place over the page: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The Rate this chat button opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same RateFields component and share the block's state, so a face pressed in the copy is there in the real one. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.
Why are the faces drawn instead of emoji?
Emoji look different on every system and can't take the theme's colours, and a screen reader says what each one is called, not what it means on a scale. Each face is an inline SVG of three strokes in the current text colour, hidden from assistive technology, and its button carries the name: Very bad, Bad, Okay, Good, Great. The group is single-choice and never empties: pressing the chosen face again does nothing, so a recorded score can't be lost by accident. The words at the ends are visible, and the chosen word is read out.
What does it record, and when?
The score is recorded the moment a face is pressed, not at Done, so a person who taps and closes has still answered. Closing says "Rating kept: Good" in a polite message, and the header button becomes Change rating. The comment is separate: it is kept when you press Done or Add comment, and the drawn chat then says "You rated this chat: Good, with a comment". Nothing is sent. In your app post the score from rate the moment it changes, and the comment from finish.
How is the comment prompt chosen?
From the score, in promptOf: one or two asks what went wrong, three asks what would have helped, four or five asks what worked well. Before a face is chosen the box is disabled and says "Pick a face first", and its height never changes, so the faces don't move when it wakes. The button says "Choose a face first" until then and answers a press by saying so and putting focus on the faces.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus stays inside until it closes, and Escape and the close button return focus to the button that opened it. Arrow keys move between faces and press one; Tab goes on to the comment. The button and the dialog sit inside DialogTrigger, controlled with isOpen and onOpenChange, and the title is a level 2 heading. The copy shown on load moves no focus; Escape inside it and its close button close it and put focus on Rate this chat.
How does it look on a phone?
The copy shown on load docks to the foot of the frame as a bottom sheet, the faces and the comment above the button. The faces keep touch targets of 44 px, 48 from the small breakpoint, and stay in one row down to 320 px. The real dialog keeps the Dialog's own phone layout: a sheet stuck to the bottom of the screen.