Getting Started
PollCatch is a lightweight, customizable feedback widget library built with Web Components. It supports multiple widget types including star ratings, reactions, NPS scores, and polls. Use standalone widgets to get started instantly with no account, or create account-managed widgets through the dashboard for persistent data and remote management.
Features
- Two modes: standalone widgets (no account) and account-managed widgets (dashboard-managed)
- Multiple widget types: Stars, Reactions, NPS, and Polls
- Fully customizable via HTML attributes and CSS variables
- Accessible and responsive by default
- Real-time statistics and chart visualization
- WCAG-compliant color contrast
- Works with any framework or vanilla HTML
Installation
CDN (Recommended)
Add the script tag to your HTML head or body:
<script type="module" src="https://cdn.jsdelivr.net/npm/pollcatch@latest"></script>NPM
npm install pollcatch
# or
pnpm install pollcatchStandalone vs. Account-Managed Widgets
PollCatch offers two ways to use widgets, depending on whether you have an account.
Standalone Widgets
Standalone widgets require no account. All configuration is done via HTML attributes, and the widget is identified by a name attribute (minimum 5 characters). Data is retained for 30 days.
<poll-catch
type="stars"
name="my-feedback"
question="Rate our service"
num-stars="5"
star-color="#ffc107"
show-stats="after-vote">
</poll-catch>Account-Managed Widgets
Account-managed widgets are created and managed from your PollCatch dashboard. In your HTML, you only need a widget-id attributeโall other settings (question, type, colors, options) are loaded from the server. Update your widget anytime without changing code.
<poll-catch type="stars" widget-id="abc123xyz"></poll-catch>Use the metadata attribute to segment responses by page, variant, or user group. The dashboard shows a per-meta breakdown for account-managed widgets.
<!-- Segment responses by page -->
<poll-catch type="stars" widget-id="abc123xyz" metadata="pricing-page"></poll-catch>Comparison
| Feature | Standalone Widgets | Account-Managed Widgets |
|---|---|---|
| Identifier | name attribute | widget-id attribute |
| Configuration | HTML attributes | Dashboard (remote) |
| Account required | No | Yes (free) |
| Data retention | 30 days | Persistent |
| Remote updates | No (change HTML) | Yes (via dashboard) |
| Analytics | Basic (stats display) | Full (dashboard reports) |
| Metadata segmentation | No | Yes (metadata attribute) |
| Domain restrictions | No | Yes (project-level whitelist) |
| Widget pages | No | Yes (shareable public URL) |
| CSV export | No | Yes (dashboard) |
Note: If both widget-id and name are present, widget-id takes precedence.
Basic Usage
All widgets use the <poll-catch> component with different type attributes:
<!-- Star rating -->
<poll-catch type="stars" question="Rate our service" num-stars="5"></poll-catch>
<!-- NPS widget -->
<poll-catch type="nps" question="How likely are you to recommend us?"></poll-catch>
<!-- Poll widget -->
<poll-catch type="poll" question="Favorite color?" options="Red,Blue,Green"></poll-catch>
<!-- Reaction widget -->
<poll-catch type="reaction" question="Was this helpful?" options="๐,๐"></poll-catch>Stars Rating Widget
A flexible star rating component with customizable number of stars and styling.
Example
<poll-catch
type="stars"
name="satisfaction"
question="How satisfied are you?"
num-stars="5"
star-color="#ffc107"
show-stats="after-vote">
</poll-catch>Stars-Specific Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| num-stars | number | 5 | Number of stars to display (1-10) |
| star-color | string | #ffc107 | Star fill color |
| star-hover-color | string | - | Star hover state color (falls back to star-color) |
| star-size | string | - | Size: xs, sm, md, lg, xl, or CSS value (responsive by default) |
| icon | string | โ | Icon to display (emoji or SVG) |
Reaction Widget
An emoji-based reaction component for emotional feedback with support for custom icons.
Example
<poll-catch
type="reaction"
name="article-reaction"
question="React to this article"
options="๐,โค๏ธ,๐,๐ฎ,๐ข,๐ก"
show-counts="true"
show-stats="always">
</poll-catch>Reaction-Specific Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| options | string | ๐,๐ | Comma-separated reaction options |
| compact | boolean | false | Use compact mode with popover |
| popup-position | string | auto | Popover position: top, bottom, auto |
| show-counts | boolean | false | Show count for each reaction |
| reaction-size | string | - | Size: xs, sm, md, lg, xl, or CSS value (defaults to 1em) |
NPS Widget
A Net Promoter Score component for collecting loyalty metrics on a 0-10 scale.
Example
<poll-catch
type="nps"
name="nps-score"
question="How likely are you to recommend us?"
min-label="Not likely"
max-label="Very likely"
show-stats="after-vote">
</poll-catch>NPS-Specific Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| min-label | string | Not at all | Label for minimum value (0) |
| max-label | string | Extremely | Label for maximum value (10) |
| min-max-position | string | top | Label position: top or bottom |
| thank-you-msg | string | - | Message shown after submission |
Poll Widget
A component for single or multiple choice poll questions.
Example
<poll-catch
type="poll"
name="features-poll"
question="Which features do you want?"
options="Dark Mode,Mobile App,API Access,Integrations"
multiple="true"
show-stats="always"
show-chart="true">
</poll-catch>Poll-Specific Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| options | string | - | Comma-separated poll options |
| multiple | boolean | false | Allow multiple selections |
| compact | boolean | false | Use compact display mode |
| button-text | string | Submit | Text for submit button |
| thank-you-msg | string | - | Message after submission |
Custom Poll Options with Icons
<poll-catch type="poll" name="work-location" question="Where do you prefer to work?">
<poll-catch-option value="office" icon="๐ข" tooltip="Work from office">Office</poll-catch-option>
<poll-catch-option value="home" icon="๐ " tooltip="Work from home">From Home</poll-catch-option>
<poll-catch-option value="hybrid" icon="๐" tooltip="Mix of both">Hybrid</poll-catch-option>
</poll-catch>Universal Attributes
These attributes are available for all widget types.
| Attribute | Type | Default | Description |
|---|---|---|---|
| type | string | - | Required for standalone widgets. Widget type: stars, reaction, nps, poll. Account-managed widgets load this from the dashboard. |
| name | string | - | Unique identifier for standalone widgets (min 5 characters). Not needed when using widget-id. |
| widget-id | string | - | UUID of an account-managed widget. When set, configuration is loaded from the dashboard. Takes precedence over name. |
| question | string | - | The main question or label text |
| readonly | boolean | false | Makes the widget view-only |
| inline | boolean | false | Compact trigger that opens popover/bottom-sheet (ignored by reaction widgets) |
| metadata | string | - | Segment responses by page, variant, or user group. Account-managed widgets show per-meta breakdown in the dashboard. Max 255 characters. |
Display & Layout Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| question-position | string | top | Position: top, bottom, left, right |
| question-align | string | start | Alignment: left, center, right, start, end |
| stats-position | string | varies | Statistics position: top, bottom, left, right |
| stats-align | string | varies | Statistics alignment: left, center, right |
Statistics Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| show-stats | string | never | When to show: never, after-vote, always (poll defaults to after-vote) |
| show-chart | boolean | false | Show bar chart visualization |
| stats-text | string | varies | Custom format with {count}, {avg}, {nps}, {pct} |
Stats Text Variables
{count}- Total number of votes{avg}- Average rating (stars widget){nps}- Net Promoter Score (NPS widget){pct}- Percentage value
Theming Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| primary-color | string | #2196f3 | Main brand color |
| mute-color | string | #cccccc | Disabled state color |
| text-color | string | buttontext | Text color |
| background-color | string | transparent | Background color |
| accent-color | string | #f2b200 | Accent/highlight color |
| button-radius | string | sm | Border radius: xs, sm, md, lg, xl, pill |
| size | string | md | Font size: xs, sm, md, lg, xl, or CSS value |
| css | string | - | Custom CSS to inject into the widget |
Metadata Segmentation
The metadata attribute tags each response with a string value (max 255 characters). This lets you track the same widget across different contexts without creating separate widgets.
Use Cases
- Per-page tracking โ identify which page drives the most feedback
- A/B testing โ compare responses across variants
- User segments โ tag by plan tier, role, or cohort
Example
<!-- Same widget, different metadata per page -->
<poll-catch type="stars" widget-id="abc123xyz" metadata="homepage"></poll-catch>
<!-- On another page -->
<poll-catch type="stars" widget-id="abc123xyz" metadata="pricing"></poll-catch>
<!-- A/B test variant -->
<poll-catch type="stars" widget-id="abc123xyz" metadata="checkout-v2"></poll-catch>Behavior
- The widget automatically fetches stats filtered to its metadata value
- Submitting with metadata is free on all plans
- The dashboard "Meta Insights" table (paid plans) shows per-meta counts, averages, and breakdowns
Domain Whitelist
Restrict which domains can submit data through your widgets. This is a project-level setting configured in the dashboard.
Setup
- Go to Dashboard > Project Settings > Allowed Domains
- Enter a comma-separated list of domains (e.g.
example.com, staging.example.com) - The widget renders on all domains, but submissions from unlisted domains are rejected by the server
Note: Domain whitelist is not available for standalone widgets. Only account-managed widgets support this feature.
Widget Pages
Each account-managed widget can have a shareable public page at /pages/{shortId}. This is useful for sharing feedback forms via email, social media, or anywhere you can't embed code.
How to Enable
- Open the widget detail page in the dashboard
- Click the Page button and toggle the page to enabled
- Copy the shareable URL
Customization
- Page title โ displayed at the top of the page
- Header / footer markdown โ add context above or below the widget
- Background & text color โ match your brand
- Google Fonts โ load a custom font family
- Max width โ control the page content width
All responses from the widget page use the same widget-id, so they appear alongside embedded widget responses in your analytics.
Analytics & Reporting
Account-managed widgets come with a full analytics dashboard. Standalone widgets only display basic in-widget stats with 30-day data retention.
Dashboard Features
- Stats overview โ total responses, average rating, NPS score at a glance
- 30-day response chart โ daily response volume over the last month
- Option breakdown โ see how responses distribute across choices
- Meta insights (paid) โ per-metadata counts, averages, and breakdowns
- Paginated response log โ browse individual responses with timestamp, value, metadata, and URL
Standalone vs. Account-Managed
| Capability | Standalone | Account-Managed |
|---|---|---|
| In-widget stats | Yes | Yes |
| Dashboard reports | No | Yes |
| Data retention | 30 days | Persistent |
| Meta insights | No | Yes (paid) |
CSV Export
Export all responses from any account-managed widget as a CSV file directly from the dashboard.
How to Export
- Open the widget detail page in the dashboard
- Click the Export CSV button
- The file downloads as
{widget-name}-responses.csv
CSV Columns
- Date โ timestamp of the response
- Response โ the submitted value (rating, option, score)
- Meta โ metadata tag, if provided
- URL โ the page URL where the response was submitted
CSV export is available on all account plans.
CSS Variables
Customize widget appearance using CSS custom properties. All variables are prefixed with --pc-.
Color Variables
:root {
--pc-primary-color: #2196f3;
--pc-accent-color: #f2b200;
--pc-text-color: buttontext;
--pc-mute-color: #cccccc;
--pc-background-color: transparent;
--pc-border-color: #dee2e6;
--pc-hover-color: rgba(0, 0, 0, 0.05);
}Typography Variables
:root {
--pc-font-size-base: 1rem;
--pc-font-size-small: clamp(0.6em, 0.7em, 0.8em);
--pc-font-size-large: clamp(1em, 1.1em, 1.2em);
--pc-line-height-base: 1.5;
}Widget-Specific Variables
/* Stars Widget */
poll-catch[type="stars"] {
--pc-star-color: #ffc107;
--pc-star-hover-color: /* falls back to star-color */;
--pc-stars-star-size: clamp(1.2em, calc(1.2em + 0.3vw), 2em);
--pc-stars-hover-scale: 1.15;
}
/* NPS Widget */
poll-catch[type="nps"] {
--pc-nps-button-width: clamp(1.4em, calc(1.2em + 0.3vw), 2em);
--pc-nps-button-height: clamp(1.6em, calc(1.5em + 0.5vw), 2.4em);
}
/* Reaction Widget */
poll-catch[type="reaction"] {
--pc-reaction-size: 1em;
--pc-button-radius: 990px;
}Custom CSS Injection
Use the css attribute for advanced customization. Styles are automatically scoped to the widget.
<poll-catch
type="stars"
question="Custom styled stars"
css=".stars { gap: 0.5em; } .star { filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); }">
</poll-catch>Dark Mode
Support dark mode using CSS media queries or a manual toggle:
/* Automatic dark mode */
@media (prefers-color-scheme: dark) {
:root {
--pc-primary-color: #0d6efd;
--pc-text-color: #ffffff;
--pc-background-color: #212529;
--pc-border-color: #495057;
--pc-mute-color: #6c757d;
}
}
/* Manual toggle */
[data-theme="dark"] {
--pc-text-color: #ffffff;
--pc-background-color: #212529;
}Responsive Design
Widgets are mobile-first and responsive by default. Use CSS variables for responsive customization:
:root {
--pc-font-size-base: 0.875rem;
}
@media (min-width: 768px) {
:root {
--pc-font-size-base: 1rem;
}
}
@media (min-width: 1024px) {
:root {
--pc-font-size-base: 1.125rem;
}
}Events Overview
All widgets emit three types of events:
- init - Fired when the widget is initialized and data is loaded
- change - Fired whenever the user's selection changes
- submit - Fired when data is successfully sent to the backend
Event Detail Structure
interface PollcatchEventDetail {
widgetType: string; // 'stars', 'reaction', 'nps', 'poll'
widgetId?: string; // The widget-id (account-managed widgets)
widgetName: string; // The name attribute
sessionId?: string; // Session identifier (on submit)
timestamp: string; // Unix timestamp (seconds)
url: string; // Current page URL
numericValue?: number; // For numeric widgets (stars, NPS)
textValues?: string[]; // For selection widgets (poll, reaction)
metadata?: string; // Custom metadata
}Capturing Events
JavaScript Event Listeners
const widget = document.querySelector('poll-catch[type="stars"]');
// Listen for selection changes
widget.addEventListener('change', (e) => {
const { numericValue, widgetName } = e.detail;
console.log(`${widgetName} rated: ${numericValue} stars`);
});
// Listen for successful submission
widget.addEventListener('submit', (e) => {
const { numericValue, widgetName } = e.detail;
console.log(`${widgetName} submitted: ${numericValue} stars`);
// Send to your own analytics
fetch('/api/feedback', {
method: 'POST',
body: JSON.stringify(e.detail)
});
});Global Event Handling
// Listen for all widget events on the page
document.addEventListener('submit', (e) => {
if (e.target.tagName === 'POLL-CATCH') {
const { widgetType, widgetName, numericValue, textValues } = e.detail;
console.log(`Widget: ${widgetName} (${widgetType})`, numericValue || textValues);
}
});Custom Endpoints
Send feedback data to your own server using the data-endpoint attribute or custom functions.
Using data-endpoint
<!-- Send data to your own API -->
<poll-catch
type="stars"
name="feedback"
question="Rate us"
data-endpoint="https://api.yoursite.com/feedback">
</poll-catch>Using data-func for Custom Logic
<script>
window.myDataHandler = async (projectKey, widgetName, options) => {
// Custom data fetching logic
const response = await fetch(`/api/data/${widgetName}`);
return await response.json();
};
</script>
<poll-catch
type="stars"
name="custom-widget"
question="Rate us"
data-func="myDataHandler">
</poll-catch>Framework Integration
React
import { useEffect, useRef } from 'react';
function FeedbackWidget() {
const widgetRef = useRef(null);
useEffect(() => {
const widget = widgetRef.current;
const handleChange = (e) => console.log('Changed:', e.detail);
widget?.addEventListener('change', handleChange);
return () => widget?.removeEventListener('change', handleChange);
}, []);
return (
<poll-catch
ref={widgetRef}
type="nps"
name="nps-score"
question="How likely are you to recommend us?"
show-stats="after-vote"
/>
);
}Vue
<template>
<poll-catch
type="poll"
:name="widgetName"
:question="pollQuestion"
:options="pollOptions"
@change="handleChange"
/>
</template>
<script>
export default {
data() {
return {
widgetName: 'feature-poll',
pollQuestion: 'Which feature would you like next?',
pollOptions: 'Dark Mode,Mobile App,API Access',
};
},
methods: {
handleChange(e) {
console.log('Poll changed:', e.detail);
},
},
};
</script>