Skip to content

Rendering surfaces

Can we display the survey in a BottomSheet or a dedicated page instead of a modal?

Yes. The SDK renders into any DOM element you point it to — it does not draw a modal, drawer, or bottom sheet itself. That makes it trivial to embed the survey inside whichever surface your product already uses for similar flows.

The only contract is: provide an element with an id, pass that id to form.generate(...), and the SDK will render into it.

<div id="survey-root"></div>
const form = magicfeedback.form("APP_ID", "PUBLIC_KEY");
await form.generate("survey-root");

Below are the common surface patterns.


The simplest surface — a full route in your SPA.

// React
function FeedbackPage() {
useEffect(() => {
magicfeedback.init({ env: "prod" });
const form = magicfeedback.form("APP_ID", "PUBLIC_KEY");
form.generate("survey-root", { addButton: true, addSuccessScreen: true });
}, []);
return <div id="survey-root" />;
}

Good for NPS pages, long surveys, post-purchase flows, dedicated /feedback URLs.


Render inside any modal component your design system already provides. The SDK does not care which library you use.

function FeedbackModal({ open, onClose }) {
useEffect(() => {
if (!open) return;
const form = magicfeedback.form("APP_ID", "PUBLIC_KEY");
form.generate("survey-root", {
addButton: true,
addSuccessScreen: true,
questionFormat: "slim",
afterSubmitEvent: ({ completed }) => {
if (completed) onClose();
},
});
}, [open]);
return (
<Dialog open={open} onClose={onClose}>
<DialogBody>
<div id="survey-root" />
</DialogBody>
</Dialog>
);
}

questionFormat: "slim" keeps each step compact inside a modal.


Identical pattern — the container just lives inside a drawer.

<Drawer open={open} side="right" onClose={close}>
<div id="survey-root" />
</Drawer>

Use it for inline product feedback, contextual surveys, “What can we improve?” prompts.


A native-feeling bottom sheet is just a DOM container styled to slide up from the bottom. Use any library (vaul, react-modal-sheet, framer-motion) or roll your own.

import { Drawer } from "vaul";
function FeedbackBottomSheet({ open, onClose }) {
useEffect(() => {
if (!open) return;
const form = magicfeedback.form("APP_ID", "PUBLIC_KEY");
form.generate("survey-root", { questionFormat: "slim", addButton: true });
}, [open]);
return (
<Drawer.Root open={open} onOpenChange={(v) => !v && onClose()}>
<Drawer.Portal>
<Drawer.Overlay />
<Drawer.Content>
<div id="survey-root" />
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}

For purely native bottom sheets in React Native, see React Native — there the survey runs inside a WebView placed inside the bottom sheet.


Embed the survey as part of a larger page (e.g. a feedback section at the bottom of a checkout success page).

<main>
<h1>Thanks for your purchase!</h1>
<p>While you're here, we'd love your feedback.</p>
<section>
<div id="survey-root"></div>
</section>
</main>
const form = magicfeedback.form("APP_ID", "PUBLIC_KEY");
await form.generate("survey-root", { addButton: true });

Anywhere your product already has nested content, the survey can be just one more panel.

<Tabs>
<TabPanel value="overview"></TabPanel>
<TabPanel value="feedback">
<div id="survey-root" />
</TabPanel>
</Tabs>

Mount the form when the panel becomes active, not on initial page load — otherwise you’ll generate the form into a hidden container.


If you need more than one survey on a single page, use distinct container ids and distinct form instances.

const a = magicfeedback.form("APP_ID", "PUBLIC_KEY");
const b = magicfeedback.form("OTHER_APP_ID", "OTHER_PUBLIC_KEY");
await a.generate("survey-root-a");
await b.generate("survey-root-b");

Because the SDK renders directly into the container, the cleanest way to “destroy” a survey is to remove the container from the DOM (or empty it). In React, that happens automatically when the host component unmounts.

useEffect(() => {
const form = magicfeedback.form("APP_ID", "PUBLIC_KEY");
form.generate("survey-root");
return () => {
// Container is removed by React when this component unmounts.
// Nothing else to do.
};
}, []);

If you re-render the survey in the same container, call generate() again with clearContainer semantics — by default the SDK replaces the container’s contents.


SurfaceGood forNotes
Dedicated pageLong surveys, NPS, post-purchase flowsUse questionFormat: "standard".
ModalQuick mid-session feedback, prompt after an eventUse questionFormat: "slim".
Drawer / panelContextual feedback inside an existing screenSame as modal.
Bottom sheetMobile web, native-feeling micro-surveysUse "slim" and short surveys.
Inline section”While you’re here” surveys after a primary actionKeep it short to avoid friction.
Wizard stepSurveys as part of an onboarding or guided flowMount on panel activation.
React NativeNative appsUse a WebView — see RN reference.