Feedback modal
A simple alternative to the element picker: a button opens a form with a message, an optional email and optional attachments.
Use the modal when you want a classic "Send feedback" form. Use the feedback widget when every report should start by pointing at something on the page. Both can be mounted together; each feedback records which one sent it.
Basic setup
Place it once, under RoastnestProvider. In cloud mode submissions, screenshots and recordings go to your dashboard.
import { FeedbackModal } from "@roastnest/react";
export function Layout({ children }) {
return (
<>
{children}
<FeedbackModal />
</>
);
}In self-hosted mode you receive the message and the attachment blobs.
<FeedbackModal
mode="self-hosted"
onSubmit={async ({ message, email, attachments, metadata }) => {
const form = new FormData();
form.append("message", message);
if (email) form.append("email", email);
attachments.forEach((a) => form.append(a.type, a.blob));
const res = await fetch("/api/feedback", { method: "POST", body: form });
return res.ok; // false shows the error state
}}
/>Attachments
Three optional buttons sit under the message box. Each can be turned off.
Select area
Works exactly like the feedback widget: hovered elements are outlined, and a click picks one. It attaches the element on its own and the full page with that element outlined in red. The click never reaches your page, so picking a link or button does not trigger it. Esc cancels.
Screenshot
Attaches a screenshot of the full page. Widget UI is left out of the picture.
Record screen
The browser asks which screen, window or tab to share. The modal hides while the user reproduces the problem, and a floating bar shows the time and a Stop button. Recording also stops at maxDurationSec (2 minutes by default, 5 at most) or when the user ends sharing from the browser.
Where recording isn't available
The button is hidden automatically where the browser can't record the screen, such as iOS. Recordings are WebM (MP4 on Safari) and capped at 25 MB; the recorder uses a modest bitrate so two minutes stays well under that.
Options
<FeedbackModal
title="Found a bug?"
email="required"
message={{ placeholder: "What happened?", maxLength: 1000 }}
attachments={{
area: true,
screenshot: true,
recording: { maxDurationSec: 60 },
max: 3,
}}
popup={{ position: "bottom-right", backdrop: "none" }}
triggerButton={{ label: "Feedback", placement: "left-bottom" }}
/>| Prop | Type | Default | Description |
|---|---|---|---|
title | string | "Send feedback" | Modal heading. |
description | string | — | Line under the heading. |
email | "optional" | "required" | "hidden" | "optional" | Never asked when the user is already known (setUser with an email, or a previous submission). |
message | { placeholder?, required?, maxLength? } | required, 2000 | The message box. |
attachments | { area?, screenshot?, recording?, max? } | all on, max 5 | recording also takes { maxDurationSec, audio }. |
popup | { position?, backdrop?, closeOnBackdropClick? } | center, dim | Also set on the dashboard (Customize → Feedback Widget → Feedback Modal). Props override the dashboard field by field. backdrop: "none" leaves the page usable and doesn't take focus. |
hideTriggerButton | boolean | false | Open it only from your own UI. |
triggerButton | { label?, placement?, mode?: "default" | "icon" } | right-bottom | The floating button. |
submitLabel / cancelLabel | string | — | Button text. |
successMessage / errorMessage | string | — | Shown after sending. |
theme | WidgetTheme | — | Theming |
icons | FeedbackModalIcons | — | Custom icons |
onOpen / onClose | () => void | — | Lifecycle callbacks. |
onSubmitted | ({ trackingUrl? }) => void | — | After a successful send. trackingUrl is set in cloud mode. |
Opening from your own button
import { FeedbackModal, useFeedbackModal } from "@roastnest/react";
// Mount it once, without the floating button…
<FeedbackModal hideTriggerButton />
// …and open it from anywhere.
function HelpMenu() {
const { open } = useFeedbackModal();
return <button onClick={open}>Report a problem</button>;
}useFeedbackModal() works in any component and returns { isOpen, open, close, toggle, setTriggerButtonVisibility }. Outside React use feedbackModalController. See Controlling widgets.
Self-hosted payload
interface FeedbackModalSubmission {
message: string;
email?: string;
attachments: Array<{
id: string;
type: "selected-screenshot" | "full-screenshot" | "screen-recording";
blob: Blob;
mimeType: string; // "image/png", "video/webm" or "video/mp4"
durationMs?: number; // recordings only
}>;
metadata: {
device, page, screen, engine, browser, mobile, os
};
}In the dashboard
Each submission is a normal feedback: it lands in Feedbacks with status, priority, comments and integrations. It is labelled Feedback modal, the email becomes the reporter, screenshots show as images and recordings play inline. Use the Filters menu to show only modal feedback or only feedback with a recording.
Drafts survive a close
Closing the modal keeps the message and attachments until the page reloads. A successful send clears them.