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:
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.