Feedback Widget

The InputBuffer feedback widget is a small script you add to any web page: a docs site, a dashboard, an API reference, or your own app. The widget collects thumbs-up and thumbs-down votes and written comments, and sends them straight to InputBuffer, so you don't need a server of your own. If you scroll to the bottom of this page you'll see an example of the widget.

This page has two parts. The getting started guide gets the widget onto one of your pages. The reference documentation after it lists every option the widget takes.

Getting started guide

By the end of this guide you'll have a widget token, the widget on one of your pages, and a test comment on your Feedback page. Setup takes two steps: create a widget token, then add the widget to a page.

If you use a coding assistant, it can add the widget to your app for you. Here's the prompt:

Hand this to your AI assistant
Works with Claude Code, Cursor, and similar tools.
View prompt
Add InputBuffer feedback collection to my app using the @inputbuffer/feedback widget.

1. Drop in the inline feedback bar as a web component (simplest option, no build step):

<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/bar.js"></script>

<inputbuffer-feedback
  api-key="YOUR_WIDGET_TOKEN"
  label="Was this helpful?">
</inputbuffer-feedback>

This renders inline thumbs up/down buttons. After a vote, a short follow-up form appears
so the user can add context, and the feedback is sent to InputBuffer.

2. To attach feedback to a specific page or endpoint, add a target. A target is what makes thumb
votes countable, so without one a vote opens the follow-up form but records nothing.

On the web component, set target-type plus the metadata attributes for that type. target-page-url
defaults to the current page, so rating a docs page takes one attribute:

<inputbuffer-feedback
  api-key="YOUR_WIDGET_TOKEN"
  label="Was this helpful?"
  target-type="documentation">
</inputbuffer-feedback>

An endpoint needs its identifying fields spelled out:

<inputbuffer-feedback
  api-key="YOUR_WIDGET_TOKEN"
  label="Was this helpful?"
  target-type="rest_endpoint"
  target-method="POST"
  target-path="/v1/users">
</inputbuffer-feedback>

3. To build the bar in JavaScript instead, pass the same target as an object.

If there's no build step, use the same script tag and call the global it exposes:

<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/bar.js"></script>
<script>
  const bar = InputBufferIO.createBar({
    apiKey: 'YOUR_WIDGET_TOKEN',
    label: 'Was this helpful?',
    target: {
      type: 'documentation',
      metadata: { page_url: window.location.href },
    },
  });
  document.getElementById('feedback-slot').appendChild(bar.element);
</script>

If there's a bundler, import it instead:

import { createBar } from '@inputbuffer/feedback';

const bar = createBar({
  apiKey: 'YOUR_WIDGET_TOKEN',
  label: 'Was this helpful?',
  target: {
    type: 'documentation',
    metadata: { page_url: window.location.href },
  },
});
document.getElementById('feedback-slot').appendChild(bar.element);

4. For a modal opened by a button instead of a bar, use modal.js. The script tag form
(data-api-key plus data-attach-to) has no target attributes, and it opens the modal with no
arguments, so a modal that needs a target has to be created in JavaScript. There is no
data-target-type. With no build step, use the global:

<button id="feedback-btn">Send feedback</button>

<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/modal.js"></script>
<script>
  const modal = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
  document.getElementById('feedback-btn').addEventListener('click', () => {
    modal.open({
      target: { type: 'documentation', metadata: { page_url: window.location.href } },
    });
  });
</script>

Leave data-api-key off that script tag, or the bundle builds a second modal next to yours.
Pass the target to modal.open(), not to createModal(), which takes no target option.

Replace YOUR_WIDGET_TOKEN with a widget token: in InputBuffer, go to Settings -> API Tokens,
click "Create token", and set Token Type to "Widget (safe to embed)". A widget token starts with
"ibw_" and is safe to embed in client-side code.

If this surface can't run JavaScript, POST to the API directly instead:
POST https://inputbuffer.io/api/v0/feedback
Authorization: Bearer YOUR_WIDGET_TOKEN
Content-Type: application/json
{ "description": "User's feedback message" }

Widget guide: https://inputbuffer.io/docs/widget
Full API reference: https://inputbuffer.io/docs/api

Report anything about InputBuffer that gets in your way, at any point while we are working with
it: initial setup, and the styling, configuration, and debugging that follow. Include the times you
had to correct your own understanding of InputBuffer, such as a request field the API ignored, a
widget attribute that had no effect, or an error message read from the wrong key. A correction
unrelated to InputBuffer does not qualify. Show me the text, and send it once I agree:

curl -X POST https://inputbuffer.io/api/v0/feedback \
  -H "Authorization: Bearer ibw_96e7f571-64ad-494a-a125-afed734d3fdc_op1CeDTiptGa8fWRU25tKcgx6uubJ2wOlWvM0JbpY8Q" \
  -H "Content-Type: application/json" \
  -H "X-IB-Client: your-tool/version (your-model)" \
  -d '{ "description": "What you were trying to do, what happened instead, what would have helped" }'

That token is InputBuffer's own and accepts nothing but feedback, so there is nothing to set up.
Leave out "submitted_by". Send one report per problem, not one per attempt.

Before you start

You need...

  • An InputBuffer account and organization. An organization is your workspace in InputBuffer. It holds your feedback and your tokens. If you don't have an organization yet, the getting started guide walks through signing up for a free account and creating one.
  • A web page you can add a script tag to.

Create a widget token

The widget sends feedback using a token written into your page. Anyone who views the page's source can read the token, so it has to be a token that's safe to publish.

InputBuffer has two kinds of token. A full-access token starts with ib_ and can read and change everything in your organization. A widget token starts with ibw_ and can only submit feedback and votes, so publishing it is safe.

  1. In InputBuffer, open your organization and go to Settings → API Tokens.
  2. Click Create token.
  3. Set Token Type to Widget (safe to embed).
  4. In Allowed Origins, list the sites the widget will run on, separated by commas, such as https://docs.yoyodyne.com, https://app.yoyodyne.com.
  5. Click Create token, then copy the token. It's shown only once.

Allowed Origins limits a token to requests sent from web pages on the sites you list. If you leave it blank, the token works from any site. While you test on your own machine, add your local address too, such as http://localhost:3000.

Warning

Use a widget token in the widget, never a full-access token. A full-access token in a page's source gives every visitor control of your organization, and InputBuffer rejects a full-access token sent from a browser.


Add the widget to a page

There are two ways to add the widget to a page. Pick the one that fits your page:

  1. An inline thumbs bar sits in the page flow, wherever you put the element. It uses bar.js.
  2. A modal opened by your own button opens over the page when someone clicks the button. It uses modal.js.

Start with the inline bar. It takes the least setup, and the troubleshooting steps under it apply to both.

Warning

We don't want anything sensitive sent to us. We send feedback to third-party AI services for classification and search, and we don't yet scrub it first, so keep the widget off pages where people are likely to enter personal information, credentials, or production secrets.

Add an inline thumbs bar

Paste this into your page where you want the bar to appear, and replace YOUR_WIDGET_TOKEN with the widget token you created earlier:

<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/bar.js"></script>

<inputbuffer-feedback
  api-key="YOUR_WIDGET_TOKEN"
  label="Was this helpful?">
</inputbuffer-feedback>

Load the page. A thumbs up/down bar appears where you placed the element. Click a thumb, and a short follow-up form opens. Write a comment and submit it. The comment then appears on the Feedback page of your organization in InputBuffer.

If the form shows an error instead, open your browser console, where the widget logs the reason. A 401 error means the token is missing or invalid. A 403 error means one of two things: the token is a full-access token instead of a widget token, or the page's site isn't listed in the token's Allowed Origins. Check both under Settings → API Tokens.

Open a modal from your own button

The modal uses modal.js instead, and attaches to a button you already have on the page. You don't write any JavaScript.

<button id="feedback-btn">Send feedback</button>

<script
  src="https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/modal.js"
  data-api-key="YOUR_WIDGET_TOKEN"
  data-attach-to="#feedback-btn">
</script>

Click the button, and the modal opens over the page. Write a comment and submit it. The comment then appears on the Feedback page of your organization in InputBuffer.

Attach feedback to a target

A target records what a piece of feedback is about: a docs page, an API endpoint, or a CLI command. Targets let you see which pages or endpoints draw the most complaints.

The widget you added above sends no target yet. Comments still arrive on your Feedback page, but the bar only records a thumb vote when it has a target to count the vote against.

To give the inline bar a target, add target-type="documentation":

<inputbuffer-feedback
  api-key="YOUR_WIDGET_TOKEN"
  label="Was this helpful?"
  target-type="documentation">
</inputbuffer-feedback>

Load the page and click a thumb. The bar fills in the page's URL for you, so the page now appears on the Targets page of your organization in InputBuffer. Open it there to see its votes.

For an API endpoint or a CLI command, the bar needs a few more attributes. Targets on the web component lists them.

A modal opened with data-attach-to can't attach a target. There is no data-target-* attribute, so feedback from that button arrives on your Feedback page without being counted against a page or an endpoint. Attaching a target to the modal takes a few lines of script, with no build step and no npm install. Targets on the modal shows the script.

You now have a widget token and the widget on a page, and you've seen a comment arrive on your Feedback page.


Reference documentation

The rest of this page is reference: the bundles, every attribute and option, targets, errors, and styling.

Bundles

Each way of adding the widget needs one script. Pick the smallest bundle that covers what you used:

BundleSizeWhat it includes
bar.js20 KBThe thumbs up/down bar, with an optional follow-up form
modal.js16 KBThe full-text feedback modal
widget.js35 KBBoth the bar and the modal

There are two ways to load a bundle: from the CDN, or from npm.

From the CDN

https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/bar.js
https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/modal.js
https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/widget.js

The version is pinned so a new release can't change your page without you knowing. Bump the number when you want to pick up a newer version.

From npm

npm install @inputbuffer/feedback

The package has one entry point per bundle: @inputbuffer/feedback/bar, @inputbuffer/feedback/modal, and @inputbuffer/feedback for both.


Web component reference

Use <inputbuffer-feedback> with bar.js or widget.js.

AttributeDefaultDescription
api-keyrequiredYour widget token
labelNoneThe text shown next to the thumbs, such as "Was this helpful?"
show-labeltrueSet to false to hide the text label
show-thumbstrueSet to false to hide the thumbs. The whole bar becomes one button that opens the form, with no sentiment and no reaction recorded. Ignores show-label="false"
placementinlineinline renders the bar in place; fixed pins it to the bottom of the viewport
modal-titleNoneThe heading for the follow-up form
modal-placeholderNoneThe textarea placeholder text in the follow-up form
show-title-fieldfalseShow the optional title field in the follow-up form
submitted-byNoneThe id your product has for the person writing the feedback, such as their user id. Sent as submitted_by with each written comment, up to 300 characters, and returned on the feedback item by the API
user-idNoneThe id your product has for the person voting. Sent only with thumb votes, where it limits each person to one vote per target, for all time. Usually the same value as submitted-by. When omitted, InputBuffer falls back to the IP address over a 24 hour window
target-typeNoneAttaches the bar to a docs page, an endpoint, or a CLI command, so thumb votes are counted against it. See Targeting
theme-primaryNoneThe primary color, used for buttons and focus rings
theme-backgroundNoneThe bar background color
theme-textNoneThe text color
theme-selectedNoneThe background of the selected thumb button
theme-selected-colorNoneThe icon color of the selected thumb
inject-stylestrueSet to false to disable automatic CSS injection

Script tag auto-init (data-* attributes)

Add data-api-key to any script tag pointing at modal.js or widget.js and the modal initializes automatically:

AttributeDescription
data-api-keySets up the modal on page load
data-attach-toA CSS selector for the element that opens the modal on click
data-titleThe modal heading
data-placeholderThe textarea placeholder text
data-show-title-fieldSet to true to show the optional title field
data-show-sentimentSet to true to show the thumbs up/down selector
data-submitted-byThe id your product has for the person writing the feedback, such as their user id. Sent as submitted_by with each written comment, up to 300 characters, and returned on the feedback item by the API
data-color-schemelight, dark, or auto. Defaults to auto
data-inject-stylesSet to false to disable automatic CSS injection
data-theme-primaryThe primary color
data-theme-backgroundThe modal background color
data-theme-textThe text color
data-theme-selectedThe background of the selected thumb
data-theme-selected-colorThe icon color of the selected thumb

These are all of the attributes the script tag reads. None of them sets a target, because a modal takes its target when it opens rather than from its configuration. To attach one, leave data-api-key off the script tag and create the modal yourself. See Targets on the modal.


Programmatic API

For full control, use the JavaScript API directly.

createBar(config)

createBar() builds a thumbs up/down bar and returns it, so you can place it anywhere on the page. Clicking a thumb records a reaction right away, without waiting for the follow-up form. The selected vote is saved to localStorage for 24 hours, so a returning user still sees their previous choice.

import { createBar } from '@inputbuffer/feedback';
// or: const { createBar } = window.InputBufferIO;

const bar = createBar({
  apiKey: 'YOUR_WIDGET_TOKEN',
  label: 'Was this helpful?',
  placement: 'inline',  // or 'fixed'
  colorScheme: 'auto',  // 'light' | 'dark' | 'auto'
  modalTitle: 'Share your feedback',
  modalPlaceholder: "What's on your mind?",
  target: {
    type: 'documentation',
    metadata: { page_url: window.location.href },
  },
});

document.getElementById('feedback-slot').appendChild(bar.element);

createBar config options:

OptionDefaultDescription
apiKeyrequiredYour widget token
labelNoneThe text shown next to the thumbs
showThumbstrueSet to false to hide the thumbs. The whole bar becomes one button that opens the form with no sentiment, and no reaction is recorded
placement'inline''inline' renders the bar in place; 'fixed' pins it to the bottom of the viewport
colorScheme'auto''light', 'dark', or 'auto'
targetNoneThe resource the bar collects votes on. See Targeting
modalTitle'Share your feedback'The heading for the follow-up form
modalPlaceholder"What's on your mind?"The textarea placeholder text in the follow-up form
showTitleFieldfalseShow the optional title field in the follow-up form
submittedByNoneThe id your product has for the person writing the feedback, such as their user id. Sent as submitted_by with each written comment, up to 300 characters, and returned on the feedback item by the API
userIdNoneThe id your product has for the person voting. Sent only with thumb votes, where it limits each person to one vote per target, for all time. Usually the same value as submittedBy. When omitted, InputBuffer falls back to the IP address over a 24 hour window
injectStylestrueSet to false to disable automatic CSS injection
themeNoneA theme object. See Theming

Bar instance methods:

MethodDescription
bar.on('vote', ({ sentiment }) => {})The user clicked a thumb
bar.on('open', ({ sentiment }) => {})The follow-up form opened
bar.on('submit', ({ id }) => {})The feedback was submitted. id is the InputBuffer feedback id
bar.on('close', () => {})The follow-up form closed
bar.on('error', (err) => {})The submission failed
bar.open(sentiment?)Opens the follow-up form from your own UI. Pass 'positive' or 'negative' to select that thumb, or omit it to keep whatever is already selected. This is a display action only: it does not record a reaction or emit vote
bar.close()Closes the follow-up form. A no-op if it's already closed
bar.destroy()Removes the bar and cleans up its listeners

Pair showThumbs: false with your own trigger to open the follow-up form without showing the thumbs at all:

const bar = createBar({
  apiKey: 'YOUR_WIDGET_TOKEN',
  showThumbs: false,
  target: {
    type: 'documentation',
    metadata: { page_url: window.location.href },
  },
});

document.getElementById('feedback-slot').appendChild(bar.element);
document.getElementById('my-trigger').addEventListener('click', () => bar.open());

createModal(config)

createModal() builds a feedback modal and returns it. The modal stays hidden until you call modal.open().

import { createModal } from '@inputbuffer/feedback';
// or: const { createModal } = window.InputBufferIO;

const modal = createModal({
  apiKey: 'YOUR_WIDGET_TOKEN',
  title: 'Share your feedback',
  placeholder: "What's on your mind?",
  showTitleField: false,
  showSentiment: false,
  colorScheme: 'auto',
});

document.getElementById('my-button').addEventListener('click', () => modal.open());

createModal config options:

OptionDefaultDescription
apiKeyrequiredYour widget token
attachToNoneA CSS selector for an element that opens the modal on click
title'Share your feedback'The modal heading
placeholder"What's on your mind?"The textarea placeholder text
showTitleFieldfalseShow the optional title field
showSentimentfalseShow the thumbs up/down sentiment selector. The selection drives the modal's own UI. The feedback API has no sentiment field, so recording a vote needs the bar and a target instead
submittedByNoneThe id your product has for the person writing the feedback, such as their user id. Sent as submitted_by with each written comment, up to 300 characters, and returned on the feedback item by the API
colorScheme'auto''light', 'dark', or 'auto'
injectStylestrueSet to false to disable automatic CSS injection
themeNoneA theme object. See Theming

createModal takes no target option, unlike createBar. The modal attaches a target per open call, so pass one to modal.open(). See Targets on the modal.

Modal instance methods:

MethodDescription
modal.open(options?)Opens the modal, optionally with context. See below
modal.close()Closes the modal
modal.destroy()Removes the modal and cleans up its listeners
modal.on('submit', ({ id }) => {})The feedback was submitted. id is the InputBuffer feedback id
modal.on('close', () => {})The modal closed
modal.on('error', (err) => {})The submission failed

Use the id from the submit event to match a widget submission to its entry on your Feedback page, or to fetch it from the REST API.

Pass options to pre-populate the modal or attach feedback to a specific target:

modal.open({
  title: 'Feedback on this page',        // Override modal heading
  sentiment: 'negative',                 // Pre-select a thumb ('positive' | 'negative')
  submittedBy: 'user_12345',             // Override the submittedBy set on createModal
  prefill: {
    description: 'This section was...',  // Pre-fill textarea
  },
  target: {
    type: 'documentation',
    metadata: {
      page_url: window.location.href,
      section_heading: 'Submit your first feedback',  // optional
    },
  },
});

Targeting

Targets attach feedback to a specific resource in your product: a documentation page, a REST endpoint, or a CLI command. A target lets you see which pages or endpoints draw the most complaints.

You do not register targets ahead of time. Send the type and its metadata, and InputBuffer either finds the matching target or creates it. Two targets of the same type with the same metadata are treated as the same target, which is how votes and feedback accumulate per resource.

Note

Targets are optional for the modal: without one, feedback still arrives on your Feedback page. They matter more for the bar. A thumb click with no target still opens the follow-up form, and anything the user writes there still reaches your Feedback page, but the vote itself is not recorded anywhere.

Warning

When you set a target, metadata is required, and so are the fields marked below as required for that type. A target missing one is rejected with a 422. The web component drops an incomplete target and logs a warning to the console rather than sending it.

Documentation page

modal.open({
  target: {
    type: 'documentation',
    metadata: {
      page_url: 'https://docs.yoyodyne.com/getting-started',  // required
      section_heading: 'Authentication',  // optional
      doc_version: 'v2',                  // optional
    },
  },
});

REST API endpoint

modal.open({
  target: {
    type: 'rest_endpoint',
    metadata: {
      method: 'POST',            // required
      path: '/v1/users',         // required
      host: 'api.yoyodyne.com',   // optional
      api_version: 'v1',         // optional
    },
  },
});

CLI command

modal.open({
  target: {
    type: 'cli_command',
    metadata: {
      command: 'deploy',         // required
      subcommand: 'production',  // optional
      cli_version: '2.4.0',      // optional
      args: ['--env', 'prod'],   // optional
    },
  },
});

Pass more than one entry in args and each flag becomes its own target, so you can see which flag drew the complaint.

Targets on the modal

The modal takes a target when it opens, not when it is created, so every target reaches it through modal.open(). The script tag auto-init calls open() with no arguments, which is why data-api-key and data-attach-to on their own cannot attach one.

A static site can still send targets, with no build step and no npm install. The CDN bundle puts createModal on window.InputBufferIO, so wiring up your own button takes a few lines:

<button id="feedback-btn">Send feedback</button>

<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/[email protected]/dist/modal.js"></script>
<script>
  const modal = window.InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });

  document.getElementById('feedback-btn').addEventListener('click', () => {
    modal.open({
      target: { type: 'documentation', metadata: { page_url: window.location.href } },
    });
  });
</script>

Leave data-api-key off the script tag when you do this. With it, the bundle builds a second modal of its own, and if data-attach-to names the same button, a click opens both.

Reach for attachTo and the data-* attributes when you don't need a target, and for the snippet above when you do.

Targets on the web component

<inputbuffer-feedback> builds the same target from target-type plus the metadata attributes for that type:

AttributeApplies toMetadata field
target-typeAlldocumentation, rest_endpoint, or cli_command
target-page-urldocumentationpage_url. Defaults to the current page URL
target-section-headingdocumentationsection_heading
target-doc-versiondocumentationdoc_version
target-methodrest_endpointmethod
target-pathrest_endpointpath
target-hostrest_endpointhost
target-api-versionrest_endpointapi_version
target-commandcli_commandcommand
target-subcommandcli_commandsubcommand
target-cli-versioncli_commandcli_version
target-argscli_commandargs, as a comma separated list

Because target-page-url defaults to the current page, rating the page a reader is on takes one attribute:

<inputbuffer-feedback
  api-key="YOUR_WIDGET_TOKEN"
  label="Was this helpful?"
  target-type="documentation">
</inputbuffer-feedback>

An endpoint needs its identifying fields spelled out:

<inputbuffer-feedback
  api-key="YOUR_WIDGET_TOKEN"
  label="Was this helpful?"
  target-type="rest_endpoint"
  target-method="POST"
  target-path="/v1/users">
</inputbuffer-feedback>

Errors

The error event hands you the failure so you can log it or show your own message. API failures arrive as an ApiError with type, title, status, detail, category, and, on a validation failure, the field that was rejected. Network failures and the 10 second request timeout arrive as an ordinary Error instead.

Switch on type rather than status, because several types share a status code. The full list is in the API Errors reference.

The category value tells you who can fix the problem. The widget treats the two differently:

  • user: the person filling in the form sent something the API rejected, such as text over the length limit. The detail value is written for them, and the widget shows it in the form as-is.
  • integration: the embed itself is misconfigured, such as a full-access token in the browser or a site that is not listed in the token's Allowed Origins. The person filling in the form sees a generic message, and the widget logs the real reason to the browser console so you can find it.
import { ApiError } from '@inputbuffer/feedback';

bar.on('error', (err) => {
  if (err instanceof ApiError && err.category === 'integration') {
    console.error('Widget misconfigured:', err.type, err.detail);
  }
});

ApiError is exported from every entry point: @inputbuffer/feedback, /bar, and /modal.

Problem typeStatusWhat went wrong
unauthorized, invalid-token, invalid-token-format401The token is missing, malformed, or revoked
widget-token-restricted403A full-access ib_ token was sent from a browser. Use a widget ibw_ token
forbidden-origin403This page's site is not listed in the token's Allowed Origins. Add it under Settings → API Tokens
usage-limit-reached402The organization hit its lifetime feedback limit
missing-required-field, invalid-field-value422A field is missing or invalid. The field value in the error response names the field that was rejected
rate-limited429One IP address sent more than 30 requests in a minute
internal-error500Something failed on our end

Theming

Color scheme

The colorScheme option sets the widget to light or dark:

createModal({ apiKey: '...', colorScheme: 'dark' });
// or 'light' | 'auto' (default: 'auto' follows system preference)

Theme object

Override individual colors via the theme config option:

createModal({
  apiKey: '...',
  theme: {
    primary: '#6366f1',       // Button and focus ring color
    background: '#1e1e2e',    // Modal background
    surface: '#2a2a3e',       // Modal header background
    text: '#cdd6f4',          // Body text
    selected: '#6366f1',      // Selected thumb background
    selectedColor: '#ffffff', // Selected thumb icon color
  },
});

The same theme option is available on createBar(). For the web component, use theme-* attributes:

<inputbuffer-feedback
  api-key="..."
  theme-primary="#6366f1"
  theme-background="#1e1e2e"
  theme-text="#cdd6f4">
</inputbuffer-feedback>

CSS custom properties

When injectStyles is true (default), the widget injects its own stylesheet and exposes CSS custom properties you can override:

/* Modal: set on #ib-modal */
#ib-modal {
  --ib-primary: #6366f1;
  --ib-primary-hover: #4338ca;
  --ib-background: #1e1e2e;
  --ib-surface: #2a2a3e;
  --ib-text: #cdd6f4;
  --ib-muted: #9399b2;
  --ib-border: #45475a;
  --ib-selected: #6366f1;
  --ib-selected-color: #ffffff;
  --ib-radius: 8px;
  --ib-radius-input: 4px;
}

/* Bar: set on .ib-bar-wrapper */
.ib-bar-wrapper {
  --ib-primary: #6366f1;
}

CSS customization

When injectStyles: false, you can style everything from scratch using stable selectors.

SelectorElement
#ib-overlayThe full-screen backdrop
#ib-modalThe modal container, and the scope for the CSS variables
#ib-modal-headerThe header bar
#ib-modal-bodyThe body content area
#ib-titleThe modal heading
#ib-textareaThe feedback textarea
#ib-title-inputThe optional title input
#ib-submitThe submit button
#ib-closeThe close button
#ib-successThe success message
#ib-errorThe error message

Bar selectors

SelectorElement
.ib-bar-wrapperThe outer wrapper, and the scope for the CSS variables
.ib-barThe visible bar strip
.ib-bar-labelThe label text
.ib-bar-btnThe thumb buttons
.ib-bar-popoverThe follow-up popover
.ib-bar-headerThe popover header
.ib-bar-bodyThe popover body
.ib-bar-titleThe popover heading
.ib-bar-textareaThe feedback textarea
.ib-bar-title-inputThe optional title input
.ib-bar-submitThe submit button
.ib-bar-successThe success message
.ib-bar-errorThe error message

Next steps

Feedback from the widget now arrives on your Feedback page. Next, set up buffers, so InputBuffer sorts that feedback into topics for you.

  • Set up buffers: define the categories InputBuffer sorts feedback into, and write the rules that route it
  • Feedback from AI Agents: have AI coding agents report problems with your docs and API, using the same kind of token
  • API getting started: send feedback from your own backend, a support tool, or a script