npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qaiddev/thumbs-embed

v1.0.14

Published

Precision User Feedback

Readme

@qaiddev/thumbs-embed

A zero-dependency, lightweight feedback embed that adds thumbs up/down buttons to any website. Users can optionally target specific page elements, leave messages, and capture screenshots — all submitted to your API endpoint or the QAid.dev hosted dashboard.

Install

npm

npm install @qaiddev/thumbs-embed
import { QaidEmbed } from '@qaiddev/thumbs-embed';

const feedback = new QaidEmbed({
  endpoint: 'https://qaid.dev/api/feedback',
  apiKey: 'YOUR_API_KEY',
});

CDN / Script Tag

<script
  src="https://unpkg.com/@qaiddev/thumbs-embed/dist/qaid.umd.cjs"
  data-endpoint="https://qaid.dev/api/feedback"
  data-api-key="YOUR_API_KEY"
></script>

The embed auto-initializes when it detects a data-endpoint attribute on its script tag.

JSON Config (Script Tag)

For complex configurations, use a separate JSON config element:

<script type="application/json" data-feedback-config>
{
  "endpoint": "https://qaid.dev/api/feedback",
  "apiKey": "YOUR_API_KEY",
  "position": "bottom-left",
  "colors": {
    "positive": "#22c55e",
    "negative": "#ef4444",
    "marker": "#8b5cf6"
  },
  "text": {
    "modalTitle": "How are we doing?",
    "placeholder": "Tell us what you think..."
  }
}
</script>
<script src="https://unpkg.com/@qaiddev/thumbs-embed/dist/qaid.umd.cjs"></script>

How It Works

  1. Thumbs up/down buttons appear on your page
  2. User clicks a thumb button
  3. A targeting overlay activates — the user clicks on any page element to attach their feedback to it
  4. Optional permission to save a screenshot of the current tab.
  5. A modal appears where the user can optionally leave a more detailed message
  6. Feedback is submitted to your endpoint as a JSON POST.

If skipTargeting is true, step 3 is skipped and the modal opens immediately.

If captureScreenshot is false, we skip step 4.

Configuration

All are optional except endpoint.

We offer a Free Plan for a compatible endpoint and a dashboard to manage your site's feedback.

Core Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | endpoint | string | (required) | API endpoint URL for submitting feedback data | | apiKey | string | "" | API key for authenticating with the feedback service | | skipTargeting | boolean | false | Skip element targeting and go directly to the feedback modal |

Positioning

| Option | Type | Default | Description | |--------|------|---------|-------------| | position | string | "bottom-right" | Button position: "bottom-right", "bottom-left", "top-right", "top-left" | | offset | { x?: number, y?: number } | { x: 16, y: 16 } | Distance from viewport edge in pixels | | container | string | "" | CSS selector for a custom container element. When set, position, offset, and zIndex are ignored | | zIndex | number | 50 | z-index for the embed elements |

Appearance

| Option | Type | Default | Description | |--------|------|---------|-------------| | buttonSize | string | "medium" | Button size: "small" (36px), "medium" (48px), "large" (64px) | | buttonClass | string | "" | Custom CSS class for buttons. When set, default button styles are not applied | | incognito | boolean | false | Buttons are invisible until hovered | | modalWidth | number | 400 | Width of the feedback modal in pixels | | backdropOpacity | number | 0.3 | Opacity of the dark backdrop behind the modal (0-1) | | fontFamily | string | "system-ui, -apple-system, sans-serif" | Font family for all text | | fontSize | number | 16 | Base font size in pixels |

Colors

Pass a colors object to customize the color scheme:

new QaidEmbed({
  endpoint: '/api/feedback',
  colors: {
    positive: 'rgb(0, 200, 83)',  // Thumbs up color (default: green)
    negative: 'rgb(255, 0, 0)',   // Thumbs down color (default: red)
    marker: '#6366f1',            // Selected element outline & submit button (default: indigo)
  },
});

Colors accept hex (#ABC, #AABBCC) or rgb(r, g, b) format.

Custom Icons

Replace the default thumb icons with SVG strings or emoji:

new QaidEmbed({
  endpoint: '/api/feedback',
  positiveIcon: '<svg viewBox="0 0 24 24">...</svg>',
  negativeIcon: '<svg viewBox="0 0 24 24">...</svg>',
});

Text Customization

Every user-facing string can be overridden via the text object:

new QaidEmbed({
  endpoint: '/api/feedback',
  text: {
    tooltip: 'Any feedback? Click to start, Esc to cancel',
    bannerText: 'Click on any element to target it with your feedback',
    bannerHint: '(Press Escape to cancel)',
    modalTitle: 'Thank you for your feedback!',
    modalSubtitle: 'Would you like to add a message to help us understand your feedback better?',
    placeholder: 'Optional: Tell us more about your experience...',
    submitButton: 'Submit',
    skipButton: 'Skip',
  },
});

Screenshots

Enable automatic screenshot capture with feedback submissions:

new QaidEmbed({
  endpoint: '/api/feedback',
  captureScreenshot: true,
  screenshotOptions: {
    quality: 0.8,      // WebP compression quality (0-1)
    maxWidth: 1280,    // Max screenshot width in pixels
    maxHeight: 800,    // Max screenshot height in pixels
  },
});

Screenshots use the browser's Screen Capture API. The user will see a permission dialog. If they decline or the API is unavailable, the feedback is still submitted without a screenshot.

Script Tag Data Attributes

When using the script tag method, all config options are available as data-* attributes:

| Attribute | Maps To | |-----------|---------| | data-endpoint | endpoint | | data-api-key | apiKey | | data-position | position | | data-zindex | zIndex | | data-button-size | buttonSize | | data-button-class | buttonClass | | data-modal-width | modalWidth | | data-backdrop-opacity | backdropOpacity | | data-offset-x | offset.x | | data-offset-y | offset.y | | data-positive-color | colors.positive | | data-negative-color | colors.negative | | data-marker-color | colors.marker | | data-container | container | | data-skip-targeting | skipTargeting | | data-incognito | incognito | | data-font-family | fontFamily | | data-font-size | fontSize | | data-tooltip | text.tooltip | | data-banner-text | text.bannerText | | data-banner-hint | text.bannerHint | | data-modal-title | text.modalTitle | | data-modal-subtitle | text.modalSubtitle | | data-placeholder | text.placeholder | | data-submit-button | text.submitButton | | data-skip-button | text.skipButton | | data-positive-icon | positiveIcon | | data-negative-icon | negativeIcon |

Custom Button Container

By default, the embed creates a fixed-position container in the viewport corner. To place the buttons inside your own element:

<div id="my-feedback-spot"></div>

<script>
new QaidEmbed({
  endpoint: '/api/feedback',
  container: '#my-feedback-spot',
});
</script>

When container is set, the position, offset, and zIndex options are ignored. You control the layout.

Custom Button Styling

Use buttonClass to apply your own CSS instead of the default button styles:

new QaidEmbed({
  endpoint: '/api/feedback',
  buttonClass: 'my-feedback-btn',
});

When buttonClass is provided, default button colors, sizing, and shadows are not applied. Structural styles (display, alignment, cursor) are still applied via .qaid-btn-structural. Your class controls everything visual.

.my-feedback-btn {
  width: 40px;
  height: 40px;
  background: #1a1a2e;
  border: 2px solid #e94560;
  border-radius: 8px;
}

.my-feedback-btn:hover {
  background: #e94560;
}

API

Constructor

const feedback = new QaidEmbed(config: FeedbackConfig);

Methods

| Method | Description | |--------|-------------| | destroy() | Remove all DOM elements, event listeners, and injected styles. Safe to call multiple times. |

Payload

The embed submits feedback in two steps:

1. Initial submissionPOST to your endpoint:

{
  "feedbackType": "up",
  "pageUrl": "https://example.com/page",
  "apiKey": "YOUR_API_KEY",
  "elementSelector": "body > main:nth-child(2) > button:nth-child(3)",
  "elementText": "Submit Order",
  "consoleErrors": [
    { "message": "TypeError: Cannot read properties of undefined", "timestamp": 1704067200000, "level": "error" }
  ],
  "screenWidth": 1920,
  "screenHeight": 1080,
  "clickX": 450,
  "clickY": 320,
  "scrollX": 0,
  "scrollY": 150,
  "screenshot": "data:image/webp;base64,...",
  "visitorId": "a1b2c3d4-...",
  "elementBounds": { "x": 400, "y": 300, "width": 120, "height": 40 },
  "userAgent": "Mozilla/5.0 ..."
}

Your endpoint should return { "id": 123 }.

2. Message updatePATCH to {endpoint}/{id}:

{
  "message": "The submit button doesn't work on mobile"
}

Console Capture

The embed automatically captures up to 20 recent console.error, console.warn, and console.log calls during the user's session. These are included in the feedback payload as consoleErrors, giving you context about what went wrong before the user submitted feedback.

Console methods are restored to their originals when destroy() is called.

Element Targeting

When the user clicks on an element during targeting mode, the embed generates a stable CSS selector using this priority:

  1. Data attributesdata-comp, data-qa, data-testid, data-id (checked on the element and its ancestors)
  2. Element ID — e.g. #submit-button
  3. nth-child path — e.g. body > main:nth-child(2) > button:nth-child(3) (always unique, always valid)

The first 100 characters of the element's text content are also captured.

Responsive Behavior

  • Desktop: The feedback modal is positioned near the selected element with an arrow pointing at the click location
  • Mobile (viewport < 640px): The modal displays as a bottom sheet, sliding up from the bottom of the screen with safe-area inset support

CSS Custom Properties

The embed injects CSS custom properties you can use or override:

:root {
  --qaid-positive: rgb(0, 200, 83);
  --qaid-negative: rgb(255, 0, 0);
  --qaid-marker: #6366f1;
  --qaid-btn-size: 48px;
  --qaid-icon-size: 24px;
  --qaid-modal-width: 400px;
  --qaid-backdrop-opacity: 0.3;
  --qaid-font-family: system-ui, -apple-system, sans-serif;
  --qaid-font-size: 16px;
}

The embed also supports dark mode automatically via the light-dark() CSS function.

Hosted Dashboard

Qaid provides a hosted dashboard for managing feedback collected by this embed:

  • Feedback inbox with archiving and admin notes
  • Project management with multiple API keys
  • Team collaboration with owners, members, and invitations
  • Browser context — console errors, viewport info, user agent captured with each submission
  • Screenshot viewing for visual bug reports
  • Analytics and data retention

Plans

| | Cadet (Free) | Commander ($9/mo) | Admiral (Enterprise) | |---|---|---|---| | Projects | 1 | Unlimited | Unlimited | | Messages | 100/mo | Unlimited | Unlimited | | Data retention | 7 days | 1 year | Unlimited | | Screenshots | - | Yes | Yes | | Notifications | - | Email/Text | Email/Text | | Integrations | - | GitHub + more | Custom |

Sign up at qaid.dev. You can also self-host — the embed works with any endpoint that accepts the payload format described above.

Self-Hosting

The embed is endpoint-agnostic. Point it at your own server:

new QaidEmbed({
  endpoint: 'https://your-server.com/api/feedback',
});

Your server needs to handle:

  1. POST /api/feedback — Accept the feedback payload, return { "id": number }
  2. PATCH /api/feedback/:id — Accept { "message": string | null } or { "feedbackType": "up" | "down" }

Project Badges