Get started

The ready-made panel

If you would rather not build a form, bugbottle/ui mounts a floating button and a small dialog in a shadow root, so your CSS and its CSS never meet:

ts
import { initConsoleBuffer } from "bugbottle";
import { mountBugbottle } from "bugbottle/ui";
import { htmlToImage } from "bugbottle/html-to-image"; // optional
import { da } from "bugbottle/locales";

initConsoleBuffer();

const widget = mountBugbottle({
  endpoint: "/api/feedback",
  screenshot: htmlToImage,
  locale: da,
  brand: { name: "Mahope", logo: "/logo.svg" },
  theme: { primary: "#e11d48", radius: "8px", position: "bottom-left" },
  extra: { appVersion: "1.4.2" },
});

// widget.open(), widget.close(), widget.setLocale(en), widget.destroy()

It offers the three report types, a message, the screenshot checkbox (only when a renderer is given), the element picker, and a thank-you state. Pass trigger: "#my-feedback-button" to use your own button instead of the floating one, or trigger: false and call open() yourself. About 11.3 kB gzipped, no framework.

contact: true adds one more field, under the message: how to reach the reporter. It is off by default, because asking for an address is a promise to answer and that promise is yours to make. contact: "required" refuses to send without it, through the same inline error an empty message gets. The field is an ordinary text input with inputmode="email", for the keyboard it brings up on a phone; the type is deliberately not email, because that plus required would mark a phone number invalid and a screen reader would announce it as an error. Nothing validates what is typed — "call me on 12345678" is a perfectly good answer, and it arrives as contact on the report either way. It is personal data once it is on: see Please read this part.

"Edit picture" over the attached screenshot is the one thing the panel does not carry by itself: hand in createAnnotator and you get the button, leave it out and the canvas editor is not in your bundle at all. See Marking the picture.

Option Effect
endpoint Where the report is POSTed. Required.
screenshot A ScreenshotRenderer. Without it the screenshot row is not rendered.
annotate createAnnotator from bugbottle/annotate renders "Edit picture"; omitted or false, nothing leads to an editor and none of it is bundled.
contact true adds an optional field asking how to reach the reporter; "required" refuses to send without it. Off by default. What they type travels as contact on the report.
elementPicker false leaves the picker out. Default true.
locale, texts, messages The language, and per-string overrides of it.
theme, brand Colours, radius, position; the name and logo in the header.
types, initialType Which report types to offer, and which starts selected.
consoleFor, screenshotFor Per type: attach the console, tick the screenshot box. Both default to bugs only.
mask What to hide in the screenshot; false photographs the page as it is. See Masking.
trigger false for no floating button, or an element or selector to use your own.
shortcut The combination that opens the panel. Default mod+shift+b; false installs no listener.
shake Open the panel when the phone is shaken. Off by default; hand in onShake from bugbottle/shake, or { on: onShake, threshold, cooldownMs }. See Shake to report.
network Record the failed and slow requests while the panel is mounted. Off by default; hand in initNetwork from bugbottle/network, or { on: initNetwork, all, slowMs, maxEntries, ignore, beforeRequest }. The panel's endpoint is passed on unless you name one. The same switch as data-network. See What the network did.
perf Record the Web Vitals and the storage snapshot while the panel is mounted. Off by default; hand in initPerf from bugbottle/perf, or { on: initPerf, vitals, storage, allowValues, maxKeys }. The same switch as data-perf. See Performance and storage.
openOnError Open the panel on an uncaught error; { prefill: true } also fills the box.
queue, scrub, sign, beforeSend The same seams the plain functions take.
extra, headers, credentials, timeoutMs, fetch, parseError Passed through to buildReport and sendReport.
container, onSent, onError Where to mount, and what to do afterwards.

Accessibility. The panel is meant to be switched on without an accessibility regression, so it behaves like a dialog rather than a floating div. While it is open, focus is trapped inside the shadow root — Tab wraps at both ends — and Escape closes it; closing puts focus back on whatever opened it, the floating trigger or your own control. The report types are a radiogroup the arrow keys walk through, one stop in the tab order, and so are the drawing tools. Every control has a name: the trigger, the close button, the group of types, the screenshot note (as aria-describedby on the checkbox), each remove button, which is named after the element it removes rather than being one of several buttons called "Remove", and every control of the picture editor — whose canvas carries a name that also says which keys work on it, since nothing on screen mentions them. Closing the editor puts focus back on the button that opened it. A polite live region announces status messages, and announces the element picker starting and stopping, with the way out — that mode hides the panel and changes the pointer, neither of which a screen reader reports. Targets are at least 24x24, focus rings are visible in both colour schemes, the dark scheme lightens the accent and the error red so they hold their contrast. A reporter who asks for prefers-reduced-motion: reduce gets every state change at once rather than over time: the panel still opens, the picture still attaches and the status is still announced, none of it travels there, and a smooth scroll is switched off along with the transitions. axe-core reports no violations on the panel open in either scheme, closed, with the picture editor open in either scheme, with the contact field on in either scheme, or with reduced motion asked for. That last state is read for movement as well, because axe has no rule for it: no element in the shadow root may have a transition or a running animation longer than zero seconds. Windows High Contrast is audited too — the panel and the picture editor again under forced-colors: active, and then photographed, because axe reads a stylesheet the browser has already overridden: the picture proves the trigger still has an edge, the selected type still differs from the two beside it, its label still reads as a word and the focus ring still shows. Run the audit yourself with npm run build && npm run a11y (Chrome and puppeteer-core required). All of the announced text comes from the locale, so it is announced in the reporter's language.

Edit this page on GitHub