@scopmx/surveybase-widget
v1.3.1
Published
Survey frontend: the admin (<SurveyAdmin />) and the pop-up survey widget.
Readme
@scopmx/surveybase-widget
React frontend for Surveybase:
<SurveyAdmin />: the full admin. Create surveys, design them, set targeting, publish, and analyze results.<SurveyWidget />: the pop-up survey shown to visitors on your website.
It talks to the backend package @scopmx/surveybase-server, which you mount in your own Express app.
Options marked (next release) are in the source but not in the published
1.1.0yet.
Contents
- Install
- Imports
<SurveyAdmin />props<SurveyWidget />propsinitSurvey()(no React)- Visitor traits
- Other exports
- Browser events and storage
- Survey settings reference
- Examples
Install
npm install @scopmx/surveybase-widget @mui/material @emotion/react @emotion/styled
# optional: icons in the admin, charts in results
npm install @mui/icons-material chart.js| Peer dependency | Version | Required |
| ----------------------------------- | ------------------ | ------------------------ |
| react, react-dom | ^18.3.1 or ^19 | Yes |
| @mui/material | ^6, ^7 or ^9 | Yes |
| @emotion/react, @emotion/styled | ^11 | Yes |
| @mui/icons-material | ^6, ^7 or ^9 | Optional |
| chart.js | ^4 | Optional (result charts) |
The admin renders inside its own isolated shadow DOM, so your page styles do not leak into it and its styles do not leak out.
Imports
import { SurveyAdmin } from "@scopmx/surveybase-widget/admin";
import { SurveyWidget, initSurvey, setVisitorTraits } from "@scopmx/surveybase-widget/widget";
// or everything from the root
import { SurveyAdmin, SurveyWidget } from "@scopmx/surveybase-widget";Use the /admin and /widget entry points so public pages do not load admin code.
<SurveyAdmin /> props
<SurveyAdmin basePath="/admin" getToken={() => session.token} />| Prop | Type | Default | Description |
| -------------------- | --------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| apiBase | string | "/api/survey" | API prefix, or an origin. https://api.example.com becomes https://api.example.com/api/survey. |
| basePath | string | detected from the URL | Router prefix for the admin, for example /admin. The admin owns every path below it (/admin/surveys, /admin/surveys/:id). |
| getToken | () => string \| Promise<string> | none | Called before every request. The result is sent as Authorization: Bearer <token>. Pair it with the server authorize option. |
| getHeaders | () => object \| Promise<object> | none | Called before every request. The returned headers are added. |
| secretKey | string | none | Deprecated. Sent as x-survey-key. Anyone who opens the page can read it, and it logs a warning. Use authorize on the server, or add the header in a server proxy. |
| height | string \| number | "100vh" | Height of the admin. Pass "100%" to fill the parent (the parent needs a height), or any CSS length. |
| loadFonts | boolean | false | Load Plus Jakarta Sans from Google Fonts. Off by default because it sends admin IP addresses to Google. |
| results | object | see below | Options for the Analyze (results) pages. |
| colors | object | see below | Brand colours for the admin itself. |
| theme | MUI Theme | built-in | Replaces the whole admin theme. Prefer colors unless you need full control. |
| panelTheme | MUI Theme | built-in | Replaces the theme of the dark builder side panel. |
| header | object \| false | see below | Projects page header. false hides it. |
| defaultSurveyColor | string (hex) | none | (next release) Colour for surveys created from this admin, for example "#0D447A". Wins over the server's defaultTheme. colors.primary only styles the admin and does not change survey colours. |
| previewUrl | string (http/https URL) | none | (next release) Website shown behind the survey in the builder's live preview, for example "https://example.com". Without it the preview shows a sample page. |
Live preview behaviour (previewUrl)
- The URL fills the preview field and loads in the frame straight away.
- A URL the admin types is saved in
localStorage(keysurveybase:preview-url:<survey key>), per survey, and wins overpreviewUrlafter a reload. Clearing the field shows the sample page again. - Many sites refuse to be shown inside another page (
X-Frame-Optionsor CSPframe-ancestors). Browsers do not report this, so:- a notice "Page blank? Some sites block previews." has a Show sample page button;
- if the page never loads, the preview falls back to the sample page after 10 seconds, with a Try again button.
- To show your own site, allow the admin's domain in your site's
frame-ancestorspolicy. - The preview uses the survey's own colour. The widget's
accentColorapplies only on the real website.
results options
<SurveyAdmin
results={{
live: false,
showRespondentId: false,
traitLabels: { userKey: "Patient", clinicId: "Clinic" },
attributesHint: "Shared by the app when the patient signs in."
}}
/>| Key | Type | Default | Description |
| ------------------ | ------------------------ | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| live | boolean | true | (next release) Keep results up to date over a live connection. With false the admin makes no background requests: data loads when the page opens, when filters change, and when the Refresh button is clicked. The "Live" chip is hidden. |
| showRespondentId | boolean | true | Show the "Respondent ID" box (with a copy button) on each response. |
| traitLabels | Record<string, string> | {} | Display names for visitor trait keys, for example { userKey: "User" }. Unlisted keys show as is. |
| attributesHint | string \| false | "Sent by your website with setVisitorTraits." | (next release) Hint under "Visitor attributes" on a response. false or "" hides it. |
What the results pages do (no configuration needed):
- Summary tab: KPIs, drop-off, activity chart and per-question results, based on the latest 1,000 matching responses (a note says so when there are more).
- Responses tab: the list loads 30 responses at a time from the API as you scroll, with loading rows, an inline Retry on errors and "All N responses loaded" at the end. The counter shows the real total, for example "151 of 42,591".
- Filters: date range (7 days, 30 days, all time), search, device, status, question and answer. All filtering runs on the server.
- Contact form table: server-side paging (10, 25, 50 or 100 rows), sort, search across name, email, phone and company, and Copy emails (up to 5,000 unique addresses).
- Export: CSV and Excel of the loaded responses.
colors options
Styles the admin only. Every key is optional; unset keys use the defaults below. Exported as surveyAdminDefaultColors.
| Key | Default | Used for |
| ----------------- | ------------------ | ------------------------------- |
| primary | #4F46E5 | Buttons, links, selected states |
| primaryDark | #4338CA | Hover and pressed primary |
| background | #F7F8FC | Page background |
| paper | #FFFFFF | Cards, menus |
| text | #111827 | Main text |
| mutedText | #667085 | Secondary text |
| border | #E5E7EB | Dividers, outlines |
| success | #059669 | Success states |
| warning | #D97706 | Warnings |
| error | #DC2626 | Errors |
| info | #2563EB | Info notices |
| topBar | primary | Builder top bar |
| topBarText | #FFFFFF | Text on the top bar |
| panelBackground | #1b1f1e | Builder side panel |
| panelPaper | #232826 | Surfaces inside the panel |
| panelCard | #262b29 | Cards inside the panel |
| panelText | #f3f1ec | Panel text |
| panelMutedText | 60% of panelText | Secondary panel text |
| panelPrimary | #9a8fff | Accent inside the panel |
| panelBorder | 10% overlay | Panel dividers |
| rail | panelBackground | Icon rail |
| railIcon | 60% of panelText | Rail icons |
| railIconActive | panelText | Active rail icon |
header options
Controls the header of the projects page. Pass header={false} to hide it completely. Defaults are exported as surveyAdminDefaultHeader.
| Key | Type | Default | Description |
| ------------------- | ------------------------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| show | boolean | true | Show the header. |
| title | ReactNode | "Projects" | Title. |
| subtitle | ReactNode | "Create a survey, then publish it when the questions are ready." | Subtitle. |
| actions | ReactNode | null | Extra content on the right. |
| showCreate | boolean | true | Show the create button. |
| createLabel | string | "New project" | Create button text. |
| showSearch | boolean | true | Show the search box. |
| searchPlaceholder | string | "Search surveys" | Search placeholder. |
| filters | false \| Array | ["all", "live", "draft", "archived"] | Status filter buttons. Items are ids or { id, label }. false hides them. |
| defaultFilter | "all" \| "live" \| "draft" \| "archived" | first filter | Filter selected on load. |
| search | string | uncontrolled | Controlled search text. |
| filter | status id | uncontrolled | Controlled status filter. |
| onSearch | (text) => void | none | Called when the search text changes. |
| onFilterChange | (filter) => void | none | Called when the filter changes. |
| onCreate | () => boolean \| void | none | Called on create. Return false to stop the built-in "New project" dialog. |
| searchFn | (project, query) => boolean | name match | Custom search matcher. |
<SurveyWidget /> props
Shows the right live survey to each visitor, based on the targeting set in the admin. Render it once, near the root of your public layout.
<SurveyWidget
siteKey="pk_..."
apiUrl="https://api.example.com"
traits={{ userKey: user.id, plan: user.plan }}
accentColor="#0D447A"
/>| Prop | Type | Default | Description |
| --------------- | ----------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| siteKey | string | the server's only install | The public pk_ site key. Optional when one API serves one site. |
| apiUrl | string | "/api/survey" | API origin or full base. https://api.example.com becomes https://api.example.com/api/survey. |
| traits | Record<string, string \| number \| boolean \| Date> | none | Visitor traits for this render, merged with traits set through setVisitorTraits. Used for audience targeting and shown on responses. |
| respondentKey | string | the userKey trait | Stable id for the person answering. The server uses it to remember "already answered" across devices. |
| fontFamily | string | survey font | Font inside the survey card. "inherit" uses the page font. |
| accentColor | string (hex) | survey colour | (next release) Replaces every survey's own colour, for example "#0D447A". An invalid value is ignored. |
Notes:
- The widget renders in an isolated overlay, so your page CSS does not affect it.
- It hides itself while
<SurveyAdmin />is mounted on the same page. - It follows client-side navigation (
pushState,replaceState, back and forward) and re-checks URL targeting on every route change.
initSurvey() (no React)
For pages that are not React apps. Mounts one widget and returns { unmount() }. Calling it again returns the same instance.
import { initSurvey } from "@scopmx/surveybase-widget/widget";
const survey = initSurvey({
siteKey: "pk_...",
apiUrl: "https://api.example.com",
traits: { userKey: "42" },
accentColor: "#0D447A"
});
// later: survey.unmount();| Option | Description |
| ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| container | Element to render into. Defaults to #surveybase, or a new <div> appended to <body>. |
| siteKey, apiUrl, respondentKey, fontFamily, accentColor | Same as the <SurveyWidget /> props. |
| traits | Stored with setVisitorTraits before the widget mounts. |
initSurvey also installs the global visitor API on window.surveybase (see below).
Visitor traits
Traits describe the visitor (for example userKey, plan, email). They are used for audience targeting, shown on each response under "Visitor attributes", and searchable in results.
import { setVisitorTraits, getVisitorTraits, resetVisitor } from "@scopmx/surveybase-widget/widget";
setVisitorTraits({ userKey: "42", plan: "pro" }); // merge
setVisitorTraits({ plan: null }); // remove a key
getVisitorTraits(); // { userKey: "42" }
resetVisitor(); // clear all (for example on logout)| Function | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------- |
| setVisitorTraits(traits) | Merges traits. null, undefined or "" removes a key. Returns the stored traits. |
| getVisitorTraits() | Returns the stored traits. |
| resetVisitor() | Clears every trait. |
| installVisitorApi() | Adds setVisitorTraits, getVisitorTraits and resetVisitor to window.surveybase for non-module scripts. |
Rules:
- Keys: letters, numbers,
_and-, up to 64 characters. At most 30 keys. - Values: strings (trimmed, up to 500 characters), finite numbers, booleans or
Date(sent as ISO text). Anything else is dropped. - Storage:
sessionStorage(surveybase:visitor), so traits last for the browser tab session only. They can hold personal data such as an email. userKeyis the defaultrespondentKey.
Global API before the script loads:
<script>
window.surveybase = { traits: { userKey: "42" } }; // applied on load
// window.surveybase = { resetRequested: true }; // clear on load
</script>Other exports
| Export | Entry | Description |
| ----------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------- |
| resetSurveys() | widget | Clears local "answered" and "dismissed" flags and picks a survey again. Useful for testing. |
| DEFAULT_API_URL | widget | "/api/survey". |
| resolveApiBase(apiUrl) | widget | The URL rule used by apiUrl. |
| SmileyFace | widget | The smiley icon: props mood (1–5), size (px, default 40), muted, title. |
| SMILEY_SCALES, smileyScale(scale) | widget | Labels for 3- and 5-face scales. |
| surveyAdminTheme, surveyAdminPanelTheme | admin | The default MUI themes. |
| createSurveyAdminTheme(colors), createSurveyAdminPanelTheme(colors) | admin | Build themes from a colors object, to extend and pass as theme / panelTheme. |
| surveyAdminDefaultColors | admin | The colour defaults table above. |
| surveyAdminDefaultHeader | admin | The header defaults table above. |
TypeScript definitions ship in types/ for every export.
Browser events and storage
| Name | Kind | Purpose |
| ----------------------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| surveybase:open | window event (default name) | Opens a survey whose trigger is "event". The name is set per survey (targeting.eventName). Fire it with window.dispatchEvent(new Event("surveybase:open")). |
| surveybase:reset | window event | Fired by resetSurveys(). |
| surveybase:location | window event | Fired internally after pushState / replaceState. |
| surveybase:visitor | sessionStorage | Visitor traits. |
| surveybase:freq:<survey>[:<respondent>] | localStorage | "Answered" and "dismissed" timestamps used by show-again rules. |
| surveybase:preview-url:<survey> | localStorage | Last preview URL typed in the builder. |
Survey settings reference
These are set in the admin builder. They are listed so you know what the widget will do.
Question types
welcome (intro screen), single, multi, dropdown, text, rating (1–5 stars), nps (0–10), opinion (1–10 scale), ces (1–7 effort), smiley (3 or 5 faces), yes_no, ranking, matrix, contact (name, email, phone, company) and date.
Questions can have logic rules that jump to another question, end the survey, or open a URL.
Design (theme)
| Key | Values | Default |
| --------------------- | -------------------------------- | ---------------------------------------- |
| primaryColor | hex | #3d2ec5 (or the server defaultTheme) |
| position | popup, button, modal | popup |
| fontFamily | font list | Inter, system-ui, sans-serif |
| cardSize | compact, regular, large | regular |
| borderRadius | 0–32 | 20 |
| buttonStyle | filled, outline, pill | filled |
| animation | slide, fade | slide |
| enterFrom, exitTo | left, right, top, bottom | bottom |
Targeting
| Key | Values | Default |
| -------------------- | ---------------------------------------------------------------------- | ----------------- |
| trigger | immediate, delay, scroll, exit, event | immediate |
| delaySeconds | 0–600 | 5 |
| scrollPercent | 1–100 | 50 |
| urlPatterns | up to 20 paths starting with / | all pages |
| device | all, mobile, desktop | all |
| traits | up to 10 rules { key, operator: eq \| neq \| contains \| in, value } | none |
| traitsMatch | all, any | all |
| hideAfterAnswer | boolean | true |
| showAgainDays | 0–3650 (0 = never) | 0 |
| dismissDays | 0–365 | 7 |
| eventName | letters, numbers, : _ - | surveybase:open |
| responseLimit | 0–1,000,000 (0 = no limit) | 0 |
| startsAt, endsAt | YYYY-MM-DD | none |
| timezone | IANA zone, for example Asia/Kolkata | browser zone |
Examples
Admin behind your own login
// Frontend
<SurveyAdmin
basePath="/admin/surveys-tool"
getToken={() => auth.getAccessToken()}
colors={{ primary: "#0D447A" }}
defaultSurveyColor="#0D447A"
previewUrl="https://clinic.example.com"
results={{
live: false,
traitLabels: { userKey: "Patient" },
attributesHint: "Shared by the app when the patient signs in."
}}
/>// Backend: see @scopmx/surveybase-server
app.use("/api/survey", surveyApi({ authorize: (req) => verifyAdmin(req) }));Widget on a public site
<SurveyWidget siteKey="pk_..." traits={{ userKey: user?.id }} accentColor="#0D447A" />Open a survey from a button
Set the survey trigger to Event in the admin, then:
button.addEventListener("click", () => window.dispatchEvent(new Event("surveybase:open")));