Get started
The form (Vue)
The same state machine, as refs. type and message are writable, so
v-model works on them directly.
vue
<script setup lang="ts">
import { useBugReport } from "bugbottle/vue";
import { htmlToImage } from "bugbottle/html-to-image"; // optional
const report = useBugReport({
endpoint: "/api/feedback",
screenshot: htmlToImage, // leave out to disable screenshots
});
</script>
<template>
<form data-bugbottle @submit.prevent="report.submit()">
<button v-for="t in report.types" :key="t" type="button" @click="report.setType(t)">
{{ t }}
</button>
<textarea v-model="report.message" />
<label v-if="report.canScreenshot">
<input
type="checkbox"
:checked="report.includeScreenshot"
@change="report.toggleScreenshot(($event.target as HTMLInputElement).checked)"
/>
Attach a picture of this page
</label>
<img v-if="report.screenshot" :src="report.screenshot" alt="" />
<button type="button" @click="report.pickElement()">
{{ report.isPicking ? "Click anything to attach it — Esc to stop" : "Point at the element" }}
</button>
<ul>
<li v-for="(el, i) in report.elements" :key="i">
<code>{{ el.selector }}</code> {{ el.text }}
<button type="button" @click="report.removeElement(i)">×</button>
</li>
</ul>
<p role="status">{{ report.statusMessage }}</p>
<button :disabled="report.isSending">Send</button>
</form>
</template>Call report.open() when the form appears. The subscription is torn down with
the effect scope the composable was called in — the component, normally; call
report.destroy() yourself if you called it outside one. vue is an optional
peer dependency, so nothing about it reaches a project that does not use it.