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.

Edit this page on GitHub