Every feedback tool I've used has the same shape. You install a widget, the widget opens a box, the user types something into the box, and the thing that lands in your inbox is a paragraph of text with a page URL stapled to it. Then you spend twenty minutes working out which component they meant.
"The new thing is confusing"
Which new thing. There are four new things on that page. The feedback isn't really attached to anything useful. At best it's attached to a page, and a page is not the unit you ship. You ship components. Your backlog is organised by component. Your team argues about components. The inbox is still organised by URL, so somebody has to do the translation by hand, and that somebody is usually you on a Friday evening.
The fix is unglamorous: put the button inside the component it's about, and give it a name. Provider once at the root, then the widget lives in the same file as the UI it describes.
// app/providers.tsx
'use client';
import { GotchaProvider } from 'gotcha-feedback';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<GotchaProvider apiKey={process.env.NEXT_PUBLIC_GOTCHA_KEY!}>{children}</GotchaProvider>
);
}
// app/checkout-form.tsx
'use client';
import { Gotcha } from 'gotcha-feedback';
export function CheckoutForm() {
return (
<div style={{ position: 'relative' }}>
<form>{/* checkout fields */}</form>
<Gotcha elementId="checkout-form" mode="feedback" />
</div>
);
}
Live on the Gotcha demo. The button is attached to the pricing card, and the response lands in the dashboard tagged example-pricing, which is that component's id. Same mechanism as the checkout-form above.
elementId="checkout-form" is the entire mechanism. Ratings, text, and bug reports from that button arrive already tagged. You are not parsing a paragraph to guess what they were looking at; you are looking at a count on checkout-form. The wrapper needs position: relative because the button positions against its nearest positioned ancestor. Keep the button in the component file so the two move together. Rename the component and the tag comes with it. Delete the component and you don't leave an orphaned id collecting noise about something that no longer exists.
The mode only changes what the box asks for. The attachment stays the same.
<Gotcha elementId="search-results" mode="feedback" />
<Gotcha elementId="ai-suggestion" mode="vote" />
<Gotcha elementId="onboarding" mode="nps" />
<Gotcha
elementId="pricing-table"
mode="poll"
options={['Too expensive', 'Missing a feature', 'Just browsing']}
/>
Vote is the one I underestimated. Almost nobody writes a paragraph about a component they mildly dislike. A lot of people will still hit a thumb, which is a signal from people who were never going to type.
A 2-star tells you something is wrong and nothing about why. If you want the reason in the same interaction, while they are still looking at the thing:
<Gotcha
elementId="export-flow"
mode="feedback"
followUp={{
ratingThreshold: 2,
promptText: 'What made this a 1 or 2?',
}}
/>
ratingThreshold is inclusive, so 2 catches ones and twos. onNegativeVote: true does the same for a thumbs-down. A survey you email on Thursday is a different, smaller set of people giving a worse answer.
Bug reports can carry their own context: page URL, viewport, browser, language, timezone. Screenshot capture is opt-in and lazy-loaded, so you are not paying ~10KB of canvas code for users who will never file a bug.
<Gotcha elementId="app-shell" enableBugFlag enableScreenshot />
The part I actually care about more than the widget is what happens after. Most tools are where requests go to die. Someone tells you export is broken, you fix it three weeks later, they never hear. Status can move new → planned → building → shipped, and if they left an email, shipping it can send them a note. That is cheap and strangely rare.
I built this. The SDK is Gotcha (gotcha-feedback on npm). About 29KB gzipped, plus ~10KB only if you turn screenshots on. React is the only peer dependency. It talks to one origin (ours). No Segment, PostHog, Google, Sentry, cookies, fingerprinting, or third-party fonts; the glyph is inlined. Nothing is sent unless someone actually submits. There is no AI in it. The dashboard groups responses by element and shows the busiest one. That is a database query. Free tier is 500 responses a month, one project, 30-day analytics, no card. Pro is $29/month if you outgrow it. The npm package is public and you can read the bundle; the repo is not open source yet. I have not decided that question.
npm install gotcha-feedback
Sign up at gotcha.cx, grab a key, wrap the app, drop one <Gotcha /> on the component you are least sure about. There is a test-response button in the dashboard so you can see the shape of a row before anything is live. That is only the shape. Real tagged feedback shows up when your users hit that component.
If you try it, tell me what annoyed you in a comment here. You can also use the widget on gotcha.cx; it lives on the page you were looking at, tagged to that section, which is the idea.

Top comments (0)