Feedback widget
A floating trigger, an element picker, and a form that arrives with screenshots and page context already attached.
Basic setup
Render FeedbackWidget anywhere inside RoastnestProvider. It positions itself, so where you put it in the tree does not affect where it appears on screen.
import { FeedbackWidget } from "@roastnest/react";
function Layout({ children }) {
return (
<div>
{children}
<FeedbackWidget />
</div>
);
}How it works
From the user's side, reporting something takes three actions:
- Activate. Clicking the trigger puts the page into selection mode. Hovering highlights the element under the cursor.
- Select. Clicking an element anchors the report to it. The widget records its position and size, and captures a screenshot scoped to that element as well as one of the full page.
- Describe. A form opens next to the selection with a message field and an optional email field.
Screenshots are rendered client-side from the live DOM, so what you receive is what the user actually saw — their data, their viewport, their theme.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
mode | "cloud" | "self-hosted" | — | Overrides the provider's mode for this widget, and narrows the remaining props at compile time. |
customize | FeedbackCustomizeProps | — | Copy, placement and notification settings. See Customization. |
onFormSubmit | (data) => Promise<boolean> | — | Submission handler. Required in self-hosted mode, and rejected in cloud mode. |
hideTriggerButton | boolean | false | Suppresses the built-in floating button so you can drive the widget from your own UI. |
children | ReactNode | — | Optional. Rendered inside the widget's root, and unaffected if the widget's own UI throws. |
Programmatic control
useFeedback opens and closes the overlay from anywhere under the provider — a menu item, a keyboard shortcut, an empty state.
import { useFeedback } from "@roastnest/react";
function ReportBugButton() {
const { isFeedbackOpen, toggleFeedback, avoidElementClassName } = useFeedback();
return (
<button onClick={toggleFeedback} className={avoidElementClassName}>
{isFeedbackOpen ? "Close feedback" : "Report a bug"}
</button>
);
}Use avoidElementClassName on your own trigger
Any element carrying that class is excluded from selection mode. Without it, your custom button becomes a selectable target and users end up filing feedback about the feedback button.
To replace the floating button entirely, hide it and drive the widget yourself:
<FeedbackWidget hideTriggerButton />useFeedback return values
| Prop | Type | Description |
|---|---|---|
isFeedbackOpen | boolean | Whether selection mode is currently active. |
toggleFeedback | () => void | Opens the overlay if closed, closes it if open. |
avoidElementClassName | string | Apply to any element that should not be selectable during selection mode. |
setTriggerButtonVisibility | (visible: boolean) => void | Shows or hides the built-in trigger at runtime — useful on routes where it should not appear. |
Handling submissions
In self-hosted mode you receive the form data directly. screenshotBlobs is an array of { blob, type } pairs, where type is either "full-screenshot" or "selected-screenshot".
<FeedbackWidget
mode="self-hosted"
onFormSubmit={async ({ email, message, screenshotBlobs }) => {
const body = new FormData();
body.append("message", message);
if (email) body.append("email", email);
for (const { blob, type } of screenshotBlobs) {
// type is "full-screenshot" or "selected-screenshot"
body.append(type, blob, type + ".png");
}
const res = await fetch("/api/feedback", { method: "POST", body });
return res.ok;
}}
/>The boolean matters
The promise's resolved value drives the UI. Resolve true and the user sees the success message; resolve false and they see the error message and keep their draft.
Turning screenshots off
Screenshot capture walks the whole DOM, which can be slow on very large pages and occasionally undesirable on screens showing sensitive data. Either capture can be disabled independently.
<FeedbackWidget
customize={{
form: {
output: {
excludeFullPageScreenshot: true,
excludeSelectedElementScreenshot: false,
},
},
}}
/>Failure behaviour
The widget is built not to take your app down with it. Its UI sits behind an error boundary, and configuration problems — a missing project ID, a missing submit handler — are reported to the console while your page keeps rendering. Cloud configuration is fetched after mount, and your content is never gated on that request completing.