Skip to main content
opini

The widget

One script tag, one floating button, a feedback modal that drops onto any page. Everything below is a knob you might want to turn — but you don't have to.

The script tag

Paste this into the HTML of any page you want to collect feedback on. A floating button appears in the corner; click it and a feedback modal opens.

html
<script
  src="https://opini.dev/widget.js"
  data-opini-key="pk_live_..."
  data-opini-label="Send feedback"
  data-opini-position="bottom-right"
  data-opini-theme="light"
  data-opini-accent="#C2410C"
  defer></script>

Running Opini on a different origin? Replace opini.dev with your own host. The widget figures out where to send submissions from there.

Options

All the knobs live on the script tag as data-* attributes. Only the key is required.

AttributeRequiredWhat it does
data-opini-keyyesYour project's public widget key (pk_live_...).
data-opini-labelnoText on the floating button. Defaults to "Send feedback".
data-opini-positionnoWhich corner to park the button in. bottom-right (default), bottom-left, top-right, top-left.
data-opini-themenolight (default) or dark. Pick whichever matches your page.
data-opini-accentnoYour brand color for the submit button and focus rings. Any CSS color — hex, rgb(), or a name.
data-opini-basenoPoint at a different API host. Rarely needed — the script works this out from its own src.

Server-side customization

The data-* attributes above are the fallback. On mount the widget fetches its project config from your Opini instance and lets an admin override a handful of strings without touching the script tag on your page:

  • widget_label — the floating button text.
  • widget_headline — the modal's heading.
  • widget_placeholder — the textarea placeholder copy.
  • widget_submit_label — the submit button.
  • widget_thanks — the thank-you state after a successful send.
  • accent_hex, widget_theme, widget_position — visuals.

Precedence: server wins, data-* fills the gaps, hard-coded defaults catch the rest. So operators can retheme every widget across every customer site from a single place without asking anyone to re-paste a script tag.

If the config fetch fails — offline, blocked, CORS — the widget still mounts with attribute + default values. The network call is never on the critical path.

Identifying users

Tell Opini who's sending feedback — so it lands in Triage with a name and anything else you care to pass along.

javascript
window.opini.identify({
  email: "[email protected]",
  name: "Ada Lovelace",
  plan: "pro",
  signed_up_at: "2024-11-02"
});

A few notes:

  • email — shows up in Triage only. Never rendered on public pages.
  • name — used as the display name in Triage and on admin views.
  • Everything else (plan, signup date, anything) rides along as extra context on the Triage detail panel.

Call it whenever you know who the user is — on page load, after login, doesn't matter. The last call before a submission wins.

Theming

Two knobs. data-opini-theme picks light (default, white surface) or dark (inverted). The widget lives inside a shadow root, so your page's CSS can't bleed in and break it.

data-opini-accent is your brand color — it paints the submit button and the focus rings. Any valid CSS color works.

Follow-ups

Triage isn't a one-way inbox. Every piece of feedback carries a follow-up button in the admin view — ask the submitter a clarifying question and they'll get a one-click reply link in their email. No account, no login, just one focused page with the original feedback excerpt, the question, and a textarea.

The reply lands back on the same feedback row. Follow-up links expire after 30 days and can only be answered once. Full page at /docs/follow-ups.

Accessibility

Everything you'd hope for is already taken care of:

  • The modal announces itself to screen readers as a dialog with a labelled heading.
  • Keyboard users can tab through the form without leaking focus back to the page; Escape closes the modal and returns focus to the button that opened it.
  • The floating CTA is a real button — no div-pretending-to- be-a-button tricks, no tap targets smaller than a fingertip.
  • The accent color flows into focus rings, so keyboard-first users always know where they are.

Removing it

Delete the script tag. The widget doesn't touch localStorage, sessionStorage, or cookies on your page, so there's nothing left behind.

Advanced

You probably don't need any of this. Open if you do.

Running a tight Content Security Policy?

Here's what to allow (assuming Opini is hosted at opini.dev):

http
Content-Security-Policy:
  script-src 'self' https://opini.dev;
  connect-src 'self' https://opini.dev;
  style-src 'self' 'unsafe-inline';

The widget injects its styles inline into its shadow root, so 'unsafe-inline' is needed on style-src until we ship nonce-based style loading. No eval, no external images, no frames.

Exact bundle size

The widget is a single script — no runtime deps, no lazy chunks, nothing to wire up beyond the tag.

  • Gzipped: ~4.2 KB
  • Uncompressed: ~12 KB