KQ-1328 · FEEDBACK/WIDGET
Feedback Widget
A feedback widget docked to the edge of a page: its tab opens a panel on glide with a row of drawn faces that wake as the pointer or focus reaches them, the mouth curving into its mood on snap and the pigment rising from grey, the chosen one landing on recoil; a press slid along the row scrubs through them. A choice glides open a text box whose question answers the mood and an optional screenshot that flashes the page and flies its frame into the panel on glide; Send folds the note in half about its crease on glide, tucks it into the tab on the exit ease, and the tab lands on recoil to say thanks. The faces are a radio group (arrows move and choose), Tab stays inside the panel, and Escape closes it and returns focus to the tab.
- Coldbrook depot08:10
- Basinworks yard09:25
- Waylight market10:40
Drag stops to reorder. Times update as the route changes.
panel open · pick a face
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/feedback-widget
Props
- open / defaultOpen
- boolean
- default — / false
- The panel is open.
- onOpenChange
- (open: boolean) => void
- default —
- Fires from the tab, the close button, Escape or a finished send.
- value / defaultValue
- FeedbackDraft
- default — / emptyFeedback
- The draft: rating (1 to the number of faces, or null), message and screenshot.
- onValueChange
- (draft: FeedbackDraft) => void
- default —
- Fires from the face, keystroke or attachment that changed the draft, and with an empty draft after a send.
- onSubmit
- (feedback: FeedbackDraft & { scale: number }) => void | Promise<void>
- default —
- Sends the feedback. A promise holds Send pending; a rejection's message is shown and Send retries.
- onCapture
- () => FeedbackShot | Promise<FeedbackShot>
- default —
- Takes the screenshot (name and an image src). Without it a drawn sketch of the page is attached.
- faces
- "five" | "three" | "two"
- default "five"
- How many faces the rating offers.
- dock
- "right" | "left" | "bottom"
- default "right"
- The edge the tab sits on; the panel opens beside it (above it for bottom).
- steps
- "guided" | "single" | "quick"
- default "guided"
- Faces first and the rest after a choice, everything at once, or faces only where a choice sends.
- title
- string
- default "How is it going?"
- The panel's question and accessible name.
- prompts
- Partial<FeedbackPrompts>
- default defaultFeedbackPrompts
- What the text box asks after a low, middling or high rating.
- placeholder
- string
- default "A sentence or two is plenty."
- The text box's hint.
- maxLength
- number
- default 280
- The longest message; the counter turns warn in the last 10%.
- screenshot
- boolean
- default true
- Offer to attach a screenshot.
- tabLabel
- string
- default "Feedback"
- The tab's word.
- sentLabel
- string
- default "Thanks"
- What the tab and the folded note say once it is sent.
- thanksHold
- number
- default 1600
- How long the tab says thanks, in ms; it waits while the page is hidden.
- fixed
- boolean
- default false
- Dock to the viewport instead of the page passed as children.
- children
- React.ReactNode
- default —
- The page the widget docks to.
- label
- string
- default —
- The panel's accessible name when it should differ from the title.
- sound
- boolean
- default false
- A pop for each face chosen (pitched by rating) and for the note landing in the tab; a swish as the panel opens, closes and folds.
- disabled
- boolean
- default false
- Shows the tab but takes no input.
- className
- string
- default —
- Classes for the root, which wraps the page.
| Prop | Type | Default | Description |
|---|---|---|---|
| open / defaultOpen | boolean | — / false | The panel is open. |
| onOpenChange | (open: boolean) => void | — | Fires from the tab, the close button, Escape or a finished send. |
| value / defaultValue | FeedbackDraft | — / emptyFeedback | The draft: rating (1 to the number of faces, or null), message and screenshot. |
| onValueChange | (draft: FeedbackDraft) => void | — | Fires from the face, keystroke or attachment that changed the draft, and with an empty draft after a send. |
| onSubmit | (feedback: FeedbackDraft & { scale: number }) => void | Promise<void> | — | Sends the feedback. A promise holds Send pending; a rejection's message is shown and Send retries. |
| onCapture | () => FeedbackShot | Promise<FeedbackShot> | — | Takes the screenshot (name and an image src). Without it a drawn sketch of the page is attached. |
| faces | "five" | "three" | "two" | "five" | How many faces the rating offers. |
| dock | "right" | "left" | "bottom" | "right" | The edge the tab sits on; the panel opens beside it (above it for bottom). |
| steps | "guided" | "single" | "quick" | "guided" | Faces first and the rest after a choice, everything at once, or faces only where a choice sends. |
| title | string | "How is it going?" | The panel's question and accessible name. |
| prompts | Partial<FeedbackPrompts> | defaultFeedbackPrompts | What the text box asks after a low, middling or high rating. |
| placeholder | string | "A sentence or two is plenty." | The text box's hint. |
| maxLength | number | 280 | The longest message; the counter turns warn in the last 10%. |
| screenshot | boolean | true | Offer to attach a screenshot. |
| tabLabel | string | "Feedback" | The tab's word. |
| sentLabel | string | "Thanks" | What the tab and the folded note say once it is sent. |
| thanksHold | number | 1600 | How long the tab says thanks, in ms; it waits while the page is hidden. |
| fixed | boolean | false | Dock to the viewport instead of the page passed as children. |
| children | React.ReactNode | — | The page the widget docks to. |
| label | string | — | The panel's accessible name when it should differ from the title. |
| sound | boolean | false | A pop for each face chosen (pitched by rating) and for the note landing in the tab; a swish as the panel opens, closes and folds. |
| disabled | boolean | false | Shows the tab but takes no input. |
| className | string | — | Classes for the root, which wraps the page. |