InputBuffer is a feedback platform built for developer-facing products. It collects feedback from your users and uses AI-driven categorization to surface patterns.
This package is a lightweight embeddable widget you can drop into your documentation sites, API and SDK references, CLI docs, or any other web property.
- Quick start
- Installation
- Reference
- Feedback bar
- Feedback modal
- Script tag attributes
- [
createModal(config)](#inputbufferiocreatemodal config) instance.open(options?)instance.close()instance.destroy()instance.on(event, handler)InputBufferIO.version
- Target metadata schemas
- CSS customization
- Common tasks
- License
Before you start you will need a widget API key from your InputBuffer dashboard.
There are three bundles — pick the one that matches your use case:
| Bundle | Size | What it includes |
|---|---|---|
bar.js |
18 KB | Thumbs up/down bar with optional follow-up form |
modal.js |
19 KB | Full-text feedback modal |
widget.js |
36 KB | Both bar and modal |
The simplest way to add feedback. Drop the script tag anywhere in your page, then place the web component where you want the bar to appear:
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
<inputbuffer-feedback
api-key="YOUR_WIDGET_TOKEN"
label="Was this helpful?">
</inputbuffer-feedback>The bar renders inline with thumbs up/down buttons. After a vote, a short follow-up form appears so the user can add context. On submit, the feedback is sent to InputBuffer.
| Light | Dark |
|---|---|
![]() |
![]() |
Omit the label attribute to show just the thumbs with no text:
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
<inputbuffer-feedback api-key="YOUR_WIDGET_TOKEN"></inputbuffer-feedback>| Light | Dark |
|---|---|
Add placement="fixed" to pin the bar to the bottom of the viewport — useful for documentation pages where you want persistent feedback access:
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
<inputbuffer-feedback
api-key="YOUR_WIDGET_TOKEN"
label="Was this helpful?"
placement="fixed">
</inputbuffer-feedback>If you want a full-text feedback modal triggered by a button, load modal.js with your API key and a CSS selector for the trigger element:
| Light | Dark |
|---|---|
![]() |
![]() |
<button id="feedback-btn">Send feedback</button>
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js"
data-api-key="YOUR_WIDGET_TOKEN"
data-attach-to="#feedback-btn">
</script>When the page loads the widget connects to #feedback-btn. Clicking it opens a modal with a text field and optional email field.
Submit a test message from your page and open your InputBuffer dashboard. It should appear in your feedback inbox within a few seconds, already categorized.
- Nothing happens on click: check that the
data-attach-toselector matches your button'sidexactly, including the#. - 401 error: your
data-api-keyis invalid — try again with a fresh copy from the dashboard, and if it still fails contact us.
- Customize the bar with
label,placement, and theme attributes (see Web component) - Create the bar programmatically with
createBar()for full config access (seeInputBufferIO.createBar(config)) - Listen for vote and submit events (see
bar.on(event, handler))
Load only the component you need — each is roughly half the full bundle:
<!-- Full bundle (modal + bar) — 36 KB -->
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/widget.js"
data-api-key="YOUR_WIDGET_TOKEN">
</script>
<!-- Modal only — 19 KB -->
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js"
data-api-key="YOUR_WIDGET_TOKEN">
</script>
<!-- Bar only — 18 KB -->
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>SRI note: For production deployments, add a Subresource Integrity
integrityattribute to guard against CDN compromise. Generate the hash for each pinned version with:curl -s https://cdn.jsdelivr.net/npm/@inputbuffer/feedback@0.1.0/dist/widget.js | openssl dgst -sha384 -binary | openssl base64 -AThen use
integrity="sha384-<hash>" crossorigin="anonymous"on the<script>tag.
The stylesheet is injected automatically — no separate <link> tag needed. Pass data-inject-styles="false" to opt out and load dist/modal.css or dist/bar.css yourself.
CSP note: Style injection requires
style-src 'unsafe-inline'in your Content Security Policy. If your CSP blocks inline styles, passdata-inject-styles="false"(orinjectStyles: falseincreateModal()) and load the stylesheet manually via<link>instead.
npm install @inputbuffer/feedbackImport only what you use — your bundler will tree-shake the rest:
// Modal only (~19 KB unminified)
import { createModal } from '@inputbuffer/feedback/modal';
// Bar only (~18 KB unminified)
import { createBar } from '@inputbuffer/feedback/bar';
// Full bundle
import { InputBufferIO } from '@inputbuffer/feedback';TypeScript types are included and exported from each entry point.
Chrome 111+, Firefox 113+, Safari 16.2+. The bundles target ES2019 and rely on Custom Elements v1 (used by the <inputbuffer-feedback> web component).
Each CDN bundle sets window.InputBufferIO at parse time — no DOMContentLoaded needed. What's exposed depends on which bundle you load:
| Bundle | window.InputBufferIO |
|---|---|
widget.js |
{ createModal, createBar, version } |
modal.js |
{ createModal, version } |
bar.js |
{ createBar, version } |
When using npm imports, use the named exports directly (createModal, createBar) rather than the global.
The feedback bar is an inline or fixed thumbs up/down component — a lightweight alternative to the modal. It can be used as a web component or created programmatically.
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
<inputbuffer-feedback api-key="YOUR_WIDGET_TOKEN" label="Was this helpful?"></inputbuffer-feedback>Supported attributes:
| Attribute | Description |
|---|---|
api-key |
Required. Your widget API key. |
api-url |
Override the API endpoint. |
label |
Text shown next to the thumbs. |
placement |
"inline" (default) or "fixed" (pins to bottom of viewport). |
theme-primary |
Primary color. |
theme-background |
Background color. |
theme-text |
Text color. |
theme-selected |
Background color of the selected thumb. |
theme-selected-color |
Icon color of the selected thumb. |
inject-styles |
Set to "false" to skip automatic style injection. |
color-scheme |
"light", "dark", or "auto". |
show-label |
"true" to show the label, "false" to hide it. |
modal-title |
Title shown above the feedback textarea. |
modal-placeholder |
Placeholder text for the feedback textarea. |
show-title-field |
"true" to show an optional title input. |
show-email-field |
"true" to show an optional email input. |
source |
Identifier for the feedback source. |
user-id |
Stable user identifier for reaction deduplication. When set, only one reaction per user is recorded per target (all-time). When omitted, deduplication falls back to IP address with a 24-hour window. |
Creates a feedback bar programmatically and returns a FeedbackBarInstance with an element property (mount it yourself) and a destroy() method.
const bar = InputBufferIO.createBar({
apiKey: 'YOUR_WIDGET_TOKEN',
label: 'Was this helpful?',
});
document.getElementById('my-slot').appendChild(bar.element);| Property | Type | Default | Description |
|---|---|---|---|
apiKey |
string | — | Required. Your widget API key. |
apiUrl |
string | — | Override the API endpoint. |
label |
string | — | Text shown next to the thumbs. |
showLabel |
boolean | true |
Set to false to show thumbs only. |
placement |
'inline' | 'fixed' |
'inline' |
fixed pins the bar to the bottom of the viewport. |
colorScheme |
'light' | 'dark' | 'auto' |
'auto' |
Force a color scheme or follow the system setting. |
theme.primary |
string | — | Primary color (buttons, focus rings). |
theme.background |
string | — | Bar and popover background color. |
theme.surface |
string | — | Popover header surface color. |
theme.text |
string | — | Text color. |
theme.selected |
string | — | Background color of the selected thumb. |
theme.selectedColor |
string | — | Icon color of the selected thumb. |
target.type |
'documentation' | 'rest_endpoint' | 'cli_command' |
— | The kind of thing the user is giving feedback on. Used for AI categorization. |
target.targetId |
string | — | Optional stable ID for this target (used for deduplication on the server). |
target.displayName |
string | — | Human-readable name shown in the InputBuffer dashboard (max 500 chars). |
target.dedupKey |
string | — | Custom deduplication key (max 500 chars). |
target.metadata |
object | — | Type-specific fields — see Target metadata schemas. |
modalTitle |
string | — | Heading for the follow-up popover. |
modalPlaceholder |
string | — | Textarea placeholder for the follow-up popover. |
showEmailField |
boolean | false |
Show/hide the email field in the follow-up popover. |
showTitleField |
boolean | false |
Show/hide the title field in the follow-up popover. |
source |
string | — | Tag identifying which of your surfaces this widget is embedded on (e.g. "ios-app", "docs-site"). Stored on every submission for filtering in the dashboard. |
userId |
string | — | Stable user identifier for reaction deduplication. When set, only one reaction per user is recorded per target (all-time). When omitted, deduplication falls back to IP address with a 24-hour window. |
injectStyles |
boolean | true |
Set to false to skip automatic style injection. |
Subscribes to bar lifecycle events.
bar.on('vote', ({ sentiment }) => console.log('Voted:', sentiment));
bar.on('open', ({ sentiment }) => console.log('Popover opened after:', sentiment));
bar.on('submit', ({ id }) => console.log('Feedback ID:', id));
bar.on('close', () => console.log('Popover closed'));
bar.on('error', (err) => console.error('Submission failed:', err));| Event | Handler signature | When it fires |
|---|---|---|
vote |
({ sentiment: 'positive' | 'negative' }) => void |
User clicks a thumb. The reaction is recorded immediately via the reactions API (if a target is configured), and the selection is persisted in localStorage for 24 hours so it survives page reloads. |
open |
({ sentiment: 'positive' | 'negative' }) => void |
The follow-up popover opens. |
submit |
({ id: string }) => void |
Feedback was submitted successfully. |
close |
() => void |
The follow-up popover closes. |
error |
(err: Error) => void |
The submission request failed. |
Removes the bar element and all event listeners.
The feedback modal is a full-text input form triggered by a button click. It can be opened programmatically or auto-initialized from the script tag.
When data-api-key is present on the script tag, the widget initializes automatically. All config is read from data-* attributes.
| Attribute | Type | Description |
|---|---|---|
data-api-key |
string | Required. Your widget API key. |
data-api-url |
string | Override the API endpoint (useful for local dev/testing). |
data-attach-to |
string | CSS selector for the element that opens the modal on click. |
data-inject-styles |
boolean | Set to "false" to skip automatic style injection. |
data-color-scheme |
string | "light", "dark", or "auto". |
data-theme-primary |
string | Primary color (buttons, focus rings). Any CSS color value. |
data-theme-background |
string | Modal background color. |
data-theme-text |
string | Modal text color. |
data-theme-selected |
string | Background color of the selected sentiment thumb. |
data-theme-selected-color |
string | Icon color of the selected sentiment thumb. |
Note:
theme.surfaceis only available via the programmatic API (createModal(config)), not as adata-*attribute.
Creates a widget instance programmatically. Returns a WidgetInstance.
const ib = InputBufferIO.createModal({
apiKey: 'YOUR_WIDGET_TOKEN',
});| Property | Type | Default | Description |
|---|---|---|---|
apiKey |
string | — | Required. Your widget API key. |
apiUrl |
string | — | Override the API endpoint (useful for local dev/testing). |
attachTo |
string | — | CSS selector. Clicking the matched element calls open(). |
injectStyles |
boolean | true |
Set to false to skip automatic style injection. |
title |
string | — | Modal heading. Omit to render no title. |
placeholder |
string | "What's on your mind?" |
Textarea placeholder text. |
showEmailField |
boolean | false |
Set to true to show an optional email field. |
showTitleField |
boolean | false |
Set to true to show an optional title field. |
showSentiment |
boolean | false |
Show thumbs up/down sentiment buttons in the modal. |
source |
string | — | Tag identifying which of your surfaces this widget is embedded on (e.g. "ios-app", "docs-site"). Stored on every submission for filtering in the dashboard. |
colorScheme |
'light' | 'dark' | 'auto' |
'auto' |
Force a color scheme or follow the system setting. |
theme.primary |
string | — | Primary color (buttons, focus rings). |
theme.background |
string | — | Modal background color. |
theme.surface |
string | — | Modal header surface color. |
theme.text |
string | — | Modal text color. |
theme.selected |
string | — | Background color of the selected sentiment thumb. |
theme.selectedColor |
string | — | Icon color of the selected sentiment thumb. |
Opens the feedback modal. Pass options to provide context at call time — useful when the same widget instance is reused across different pages or sections.
ib.open({
title: 'Was this helpful?',
target: {
type: 'documentation',
targetId: 'auth-overview',
displayName: 'Authentication overview',
metadata: {
page_url: window.location.href,
section_heading: 'Authentication',
},
},
prefill: {
description: '👍 This page was helpful',
},
});| Property | Type | Description |
|---|---|---|
title |
string | Overrides the modal heading for this open call. |
sentiment |
'positive' | 'negative' |
Pre-selects a sentiment thumb. Only relevant when showSentiment is enabled. |
target.type |
'documentation' | 'rest_endpoint' | 'cli_command' |
The kind of thing the user is giving feedback on. Used for AI categorization. |
target.targetId |
string | Optional stable ID for this target (used for deduplication on the server). |
target.displayName |
string | Human-readable name shown in the InputBuffer dashboard (max 500 chars). |
target.dedupKey |
string | Custom deduplication key (max 500 chars). |
target.metadata |
object | Type-specific fields — see Target metadata schemas. |
prefill.email |
string | Pre-populates the email field. |
prefill.description |
string | Pre-populates the textarea. |
source |
string | Overrides the source set in createModal(config) for this open call. |
Closes the modal programmatically.
Closes the modal and removes all event listeners and DOM elements. The instance cannot be reused after this call — invoke createModal() again to get a fresh one. You may have multiple WidgetInstances on a page, but opening more than one modal simultaneously is not supported (they share DOM IDs).
Subscribes to widget lifecycle events.
ib.on('submit', (result) => console.log('Feedback ID:', result.id));
ib.on('close', () => console.log('Modal closed'));
ib.on('error', (err) => console.error('Submission failed:', err));| Event | Handler signature | When it fires |
|---|---|---|
submit |
(result: { id: string }) => void |
Feedback was submitted successfully. result.id is the InputBuffer feedback ID. |
close |
() => void |
The modal was closed, either by the user or programmatically. |
error |
(err: Error) => void |
The submission request failed. |
The currently loaded widget version string.
console.log(InputBufferIO.version); // e.g. "0.1.0"The fields accepted in target.metadata depend on target.type. The server validates required fields; the client does not enforce them.
documentation
| Field | Required | Description |
|---|---|---|
page_url |
No | Full URL of the page. |
page_slug |
No | Slug or path of the page. |
section_heading |
No | Heading of the section the user is viewing. |
doc_version |
No | Documentation version string. |
rest_endpoint
| Field | Required | Description |
|---|---|---|
method |
Yes* | HTTP method (GET, POST, etc.). |
path |
Yes* | API path (e.g. /v1/users). |
host |
No | Hostname (e.g. api.example.com). |
api_version |
No | API version string. |
cli_command
| Field | Required | Description |
|---|---|---|
command |
Yes* | Top-level CLI command (e.g. auth). |
subcommand |
No | Subcommand (e.g. setup). |
cli_version |
No | CLI version string. |
* Required by the server; omitting them will result in a validation error response.
Each component uses stable selectors you can target directly in your stylesheet.
| Selector | Class alias | Element |
|---|---|---|
#ib-overlay |
— | Full-screen backdrop |
#ib-modal |
— | Modal container (scopes all CSS variables) |
#ib-modal-header |
— | Header bar |
#ib-modal-body |
— | Body area |
#ib-title |
.ib-modal-title |
Modal heading |
#ib-textarea |
.ib-modal-textarea |
Feedback text field |
#ib-email |
.ib-modal-email |
Email input |
#ib-submit |
.ib-modal-submit |
Submit button |
#ib-close |
.ib-modal-close |
Close button |
#ib-success |
.ib-modal-success |
Success message |
#ib-error |
.ib-modal-error |
Error message |
The IDs are the stable public API and will not change between releases. The .ib-modal-* class aliases are equivalent and exist for symmetry with the bar.
| Class | Element |
|---|---|
.ib-bar-wrapper |
Outer wrapper (scopes all CSS variables) |
.ib-bar |
The visible bar strip |
.ib-bar-label |
Label text |
.ib-bar-btn |
Thumb buttons |
.ib-bar-popover |
Follow-up popover container |
.ib-bar-header |
Popover header |
.ib-bar-body |
Popover body |
.ib-bar-title |
Popover heading |
.ib-bar-textarea |
Feedback text field |
.ib-bar-email |
Email input |
.ib-bar-submit |
Submit button |
.ib-bar-success |
Success message |
.ib-bar-error |
Error message |
Both components expose the same set of CSS custom properties. Set them on #ib-modal or .ib-bar-wrapper respectively, or pass them via the theme config option.
| Property | Default (light) | Default (dark) | What it affects |
|---|---|---|---|
--ib-primary |
#6366f1 |
#3A5244 |
Buttons, focus rings, borders |
--ib-primary-hover |
#4338ca |
#4E6857 |
Button hover state |
--ib-background |
#f6f6f8 |
#25272B |
Form area background |
--ib-surface |
#ffffff |
#2C3630 |
Header/card background |
--ib-text |
#111827 |
#E5E7EB |
Body text |
--ib-muted |
#8d99ae |
#9CA3AF |
Label and hint text |
--ib-border |
(primary) |
#363840 |
Border color |
--ib-selected |
(primary) |
(primary) |
Selected thumb background |
--ib-selected-color |
(surface) |
(surface) |
Selected thumb icon color |
--ib-radius |
2px |
8px |
Container border radius |
--ib-radius-input |
2px |
4px |
Input/button border radius |
// <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
document.getElementById('feedback-btn').addEventListener('click', () => {
ib.open({
title: 'Was this page helpful?',
target: {
type: 'documentation',
targetId: window.location.pathname,
displayName: document.title,
metadata: {
page_url: window.location.href,
section_heading: document.querySelector('h1')?.textContent ?? '',
},
},
});
});// <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
ib.open({
target: {
type: 'rest_endpoint',
targetId: 'POST /v1/uploads',
displayName: 'Upload a file',
metadata: { method: 'POST', path: '/v1/uploads' },
},
});// <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
ib.open({
target: {
type: 'cli_command',
targetId: 'deploy',
displayName: 'my-cli deploy',
metadata: { command: 'deploy' },
},
});// <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
const ib = InputBufferIO.createModal({ apiKey: 'YOUR_WIDGET_TOKEN' });
ib.on('submit', ({ id }) => {
analytics.track('feedback_submitted', { feedbackId: id });
});
ib.on('error', (err) => {
console.error('Feedback submission failed:', err.message);
});// <script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js" data-api-key="YOUR_WIDGET_TOKEN"></script>
InputBufferIO.createModal({
apiKey: 'YOUR_WIDGET_TOKEN',
colorScheme: 'dark',
theme: {
primary: '#818cf8',
background: '#1e1e2e',
text: '#cdd6f4',
},
}).open();<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/bar.js"></script>
<inputbuffer-feedback api-key="YOUR_WIDGET_TOKEN" label="Was this helpful?"></inputbuffer-feedback>import { createBar } from '@inputbuffer/feedback/bar';
const bar = createBar({ apiKey: 'YOUR_WIDGET_TOKEN', label: 'Was this helpful?' });
document.getElementById('my-slot').appendChild(bar.element);<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.css">
<script src="https://cdn.jsdelivr.net/npm/@inputbuffer/feedback/dist/modal.js"
data-api-key="YOUR_WIDGET_TOKEN"
data-inject-styles="false">
</script>These are two separate concepts:
-
source— where your widget is deployed. Identifies the platform or product surface, e.g."website","ios-app","chrome-extension". Use this to filter feedback by deployment environment in your dashboard. -
target— what the feedback is about. A structured object describing the specific content or feature, e.g. a REST endpoint, a docs page, or a CLI command. Use this to group feedback by the thing being reviewed, regardless of where the widget is embedded.
You can use both together:
InputBufferIO.createBar({
apiKey: 'YOUR_WIDGET_TOKEN',
source: 'website',
target: { type: 'documentation', metadata: { page_slug: 'getting-started' } },
});
---
## License
MIT


