> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sighting.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom integrations

> Use your own button, preview, or feedback form.

Install with `npm install sightingdev`. See the [Quickstart](/quickstart) for the default widget.

## Browser app with your own button

```html theme={null}
<button id="report-issue" type="button">Report an issue</button>
```

In the browser module managed by your bundler:

```js theme={null}
import { createFeedback } from 'sightingdev';

const feedback = createFeedback({ endpoint: '/api/feedback' });
const button = document.querySelector('#report-issue');
const open = () => feedback.open();
button.addEventListener('click', open);

// On teardown:
// button.removeEventListener('click', open);
// feedback.destroy();
```

Run after the button exists. A bare browser import from `'sightingdev'` requires an import map or bundler; the npm archive does not provide a standalone global/UMD script.

## React with a custom trigger

```jsx theme={null}
import { useEffect, useRef } from 'react';
import { createFeedback } from 'sightingdev';

export function ReportButton({ endpoint = '/api/feedback' }) {
  const client = useRef(null);

  useEffect(() => {
    const feedback = createFeedback({ endpoint });
    client.current = feedback;
    return () => {
      feedback.destroy();
      client.current = null;
    };
  }, [endpoint]);

  return (
    <button type="button" onClick={() => client.current?.open()}>
      Report an issue
    </button>
  );
}
```

This uses your button and Sighting's dialog. It does not also create the floating launcher.

## Next.js App Router

Create a Client Component using the [React and Next.js guide](/react). Render it from your layout or page. Keep `'use client'` at the top of the widget file; do not call `mount()` during server rendering. Implement a receiving route, or point to an external backend. Next.js does not create the receiving route when the widget is installed.

## Your own screenshot preview and form

The headless API lets you choose the layout while keeping capture and transport:

```js theme={null}
import { createFeedback } from 'sightingdev';

const feedback = createFeedback({ endpoint: '/api/feedback' });
let draft;
let previewUrl;

// Wire to an explicit "Capture" action in your UI.
async function preparePreview(imageElement) {
  try {
    const next = await feedback.capture();
    if (previewUrl) URL.revokeObjectURL(previewUrl);
    draft = next;
    previewUrl = URL.createObjectURL(draft.screenshot);
    imageElement.src = previewUrl;
    return { ok: true };
  } catch (error) {
    return { ok: false, message: error.message };
  }
}

// Wire to an explicit "Send" action after the user reviews the preview.
async function submitDraft(text) {
  if (!draft) return { ok: false, message: 'Capture a screenshot first.' };
  try {
    await feedback.send({ ...draft, text });
    return { ok: true };
  } catch {
    return { ok: false, message: 'Delivery was not confirmed. Please try again.' };
  }
}

function cleanup() {
  if (previewUrl) URL.revokeObjectURL(previewUrl);
  feedback.destroy();
}
```

These functions return state for your UI to display; they do not render buttons or messages themselves. Prevent duplicate submits while a request is in flight. Attach cleanup to your component's lifecycle.

## Reducing page context

For example, remove path identifiers before sending through your own UI:

```js theme={null}
const draft = await feedback.capture();
const report = {
  ...draft,
  context: { ...draft.context, url: location.origin },
  text: 'Checkout did not open.',
};
// After the user reviews and chooses Send:
await feedback.send(report);
```

The default dialog has no context-transform hook. Use the headless API for custom context rules.
