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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@sjbhn/feedback-widget

v1.2.3

Published

Modern feedback collection widget with data layer integration

Readme

🎯 Feedback Widget

A modern, configurable feedback collection widget built with React and TypeScript. Perfect replacement for third-party solutions like Hotjar, with full control over data and analytics integration.

✨ Features

  • 🚀 Modern Stack: Built with React, TypeScript, and modern bundling
  • 🎯 Advanced Triggers: Time, scroll, page, exit-intent, rage-click, confused navigation, dataLayer events
  • 📊 Analytics Integration: GA4, Adobe WebSDK, custom endpoints
  • 🎨 Fully Customizable: Themes, colors, fonts, positioning
  • 🌟 Custom Feedback Leaf: Personalize the minimized trigger button with custom icons, shapes, animations, and styling
  • Modern UI Components: Animated star ratings with sequential selection, NPS (0-10) scoring, smooth transitions, and golden glow effects
  • 📱 Responsive: Mobile-first design with touch support
  • 🔄 Data Layer: Push events to dataLayer for marketing analytics
  • 📦 NPM Ready: Deploy as versioned packages
  • Accessible: WCAG compliant with keyboard navigation
  • 🌍 Framework Agnostic: Works with any website or application

🚀 Quick Start

NPM Installation

npm install @sjbhn/feedback-widget

Basic Usage

import feedbackWidget from '@sjbhn/feedback-widget';

// Initialize with basic configuration
feedbackWidget.init({
  id: 'my-feedback-widget',
  version: '1.2.2',
  position: 'bottom-right',
  triggers: [
    {
      type: 'time',
      conditions: { timeDelay: 5000 }
    }
  ],
  fields: [
    {
      type: 'rating',
      label: 'How was your experience?',
      required: true
    },
    {
      type: 'text',
      label: 'Tell us more',
      required: false,
      placeholder: 'Your feedback...'
    }
  ]
});

CDN Usage

<script src="https://cdn.jsdelivr.net/npm/@sjbhn/[email protected]/dist/feedback-widget.min.js"></script>
<script>
  window.feedbackWidget.create('demo-widget', {
    triggers: [{ type: 'manual' }]
  });

  // Trigger manually
  window.feedbackWidget.show('demo-widget');
</script>

Auto-Configuration

<!-- Auto-initialize from script attribute -->
<script
  src="feedback-widget.min.js"
  data-config='{"id":"auto-widget","version":"1.0.0","triggers":[{"type":"time","conditions":{"timeDelay":3000}}],"fields":[{"type":"rating","label":"Rate us","required":true}]}'
></script>

📋 Configuration Reference

Widget Config

interface WidgetConfig {
  id: string;                    // Unique widget identifier
  version: string;               // Widget version for tracking
  position: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'center';
  triggers: TriggerConfig[];     // When to show the widget
  appearance: AppearanceConfig;  // Visual customization
  fields: FieldConfig[];         // Form fields to display
  dataLayer: DataLayerConfig;    // Data layer integration
  analytics: AnalyticsConfig;    // Analytics integrations
}

Trigger Types

// Time-based trigger
{
  type: 'time',
  conditions: {
    timeDelay: 5000,           // Delay in milliseconds
    frequency: 'once'          // 'once', 'session', 'always'
  }
}

// Scroll-based trigger
{
  type: 'scroll',
  conditions: {
    scrollPercentage: 75,      // Trigger at 75% scroll
    frequency: 'session'
  }
}

// Page-based trigger
{
  type: 'page',
  conditions: {
    pages: ['/checkout', '/product/*'],  // Specific pages or patterns
    frequency: 'once'
  }
}

// Exit-intent trigger
{
  type: 'exit-intent',
  conditions: {
    frequency: 'session'
  }
}

// Element click trigger
{
  type: 'element-click',
  conditions: {
    elementSelector: '.feedback-button',
    frequency: 'always'
  }
}

// Manual trigger
{
  type: 'manual',
  conditions: {}
}

// Rage click trigger (detect user frustration)
{
  type: 'rage-click',
  conditions: {
    rageClick: {
      clickThreshold: 3,           // Number of rapid clicks to trigger
      timeWindow: 2000,            // Time window in ms
      elementSelector: 'button',   // Target specific elements (optional)
      excludeSelectors: ['.ignore'] // Elements to exclude
    },
    frequency: 'session'
  }
}

// Confused navigation trigger (detect user confusion)
{
  type: 'confused-navigation',
  conditions: {
    confusedNavigation: {
      backAndForthThreshold: 3,    // Back/forth navigation count
      timeWindow: 30000,           // Time window for analysis
      scrollJumpThreshold: 5,      // Rapid scroll changes
      mouseMovementThreshold: 100, // Erratic mouse movements
      inactivityThreshold: 10000   // Long pauses (confusion)
    },
    frequency: 'session'
  }
}

// DataLayer event trigger (react to analytics events)
{
  type: 'datalayer-event',
  conditions: {
    dataLayerEvent: {
      eventName: 'purchase_complete',    // Event to listen for
      eventProperties: {                 // Properties to match (optional)
        value: { $gt: 100 },            // Advanced matching
        currency: 'USD'
      },
      objectName: 'dataLayer',          // DataLayer object name
      matchType: 'partial',             // 'exact', 'partial', 'exists'
      debounceTime: 1000               // Prevent duplicate triggers
    },
    frequency: 'once'
  }
}

Field Types

// Rating field (1-5 stars)
{
  type: 'rating',
  label: 'Rate your experience',
  required: true
}

// NPS field (0-10 scale)
{
  type: 'nps',
  label: 'How likely are you to recommend us?',
  required: true
}

// Text area
{
  type: 'text',
  label: 'Additional feedback',
  required: false,
  placeholder: 'Tell us more...',
  validation: {
    minLength: 10,
    maxLength: 500
  }
}

// Email input
{
  type: 'email',
  label: 'Your email',
  required: false,
  placeholder: '[email protected]'
}

// Category dropdown
{
  type: 'category',
  label: 'Feedback type',
  required: true,
  options: ['Bug Report', 'Feature Request', 'General']
}

Appearance Customization

appearance: {
  theme: 'light',              // 'light', 'dark', 'custom'
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    background: '#ffffff',
    text: '#333333',
    border: '#e0e0e0'
  },
  borderRadius: 8,
  fontSize: '14px',
  fontFamily: 'Arial, sans-serif',
  animations: true,
  customCSS: '.my-widget { box-shadow: 0 4px 20px rgba(0,0,0,0.1); }',
  feedbackLeaf: {              // 🆕 Customize the minimized feedback button
    text: 'Feedback',          // Custom text
    icon: '💬',               // Custom icon/emoji
    shape: 'tab',             // 'tab', 'circle', 'square', 'rounded'
    size: {
      width: 'auto',          // Custom width
      height: 'auto'          // Custom height
    },
    colors: {
      background: '#007bff',   // Background color (supports gradients)
      text: '#ffffff',        // Text color
      border: '#0056b3'       // Border color
    },
    positioning: {
      offsetX: '0',           // Horizontal offset from edge
      offsetY: '0'            // Vertical offset from edge
    },
    animation: {
      enabled: true,          // Enable animations
      type: 'pulse',          // 'pulse', 'bounce', 'slide', 'none'
      duration: '2s'          // Animation duration
    },
    shadow: {
      enabled: true,          // Enable shadow
      blur: '12px',          // Shadow blur radius
      color: 'rgba(0,0,0,0.15)'  // Shadow color
    },
    hideAfter: 0             // Auto-hide after milliseconds (0 = never)
  }
}

📊 Analytics Integration

Google Analytics 4

analytics: {
  ga4: {
    enabled: true,
    measurementId: 'G-XXXXXXXXXX',
    eventName: 'feedback_submitted'
  }
}

Adobe Web SDK

analytics: {
  adobeWebSDK: {
    enabled: true,
    eventType: 'web.webInteraction.linkClicks',
    schema: 'https://ns.adobe.com/sachin-s-joshi/feedback'
  }
}

Custom Endpoint

analytics: {
  custom: {
    enabled: true,
    endpoint: 'https://api.yoursite.com/feedback',
    headers: {
      'Authorization': 'Bearer your-token',
      'X-API-Key': 'your-api-key'
    }
  }
}

Data Layer Integration

dataLayer: {
  enabled: true,
  eventName: 'feedback_submitted',
  objectName: 'dataLayer',
  customProperties: {
    source: 'feedback_widget',
    version: '1.0.0'
  }
}

The widget automatically pushes events to window.dataLayer:

{
  event: 'feedback_submitted',
  feedback_data: {
    id: 'unique-id',
    type: 'rating',
    rating: 5,
    text: 'Great experience!',
    // ... other data
  },
  widget_config: {
    id: 'my-widget',
    version: '1.0.0',
    trigger: 'time_delay'
  },
  timestamp: 1634567890123
}

🔧 API Reference

Core Methods

// Initialize widget(s)
feedbackWidget.init(config);

// Create widget with default + custom config
feedbackWidget.create('widget-id', partialConfig);

// Show widget manually
feedbackWidget.show('widget-id');

// Hide widget
feedbackWidget.hide('widget-id');

// Update widget configuration
feedbackWidget.update('widget-id', updates);

// Destroy widget
feedbackWidget.destroy('widget-id');

// Get widget configuration
const config = feedbackWidget.getConfig('widget-id');

// Export configuration as JSON
const json = feedbackWidget.exportConfig('widget-id');

// Import configuration from JSON
const result = feedbackWidget.importConfig(jsonString);

// Get widget statistics
const stats = feedbackWidget.getStats();

Event Handling

// Listen for feedback submissions
feedbackWidget.on('feedbackSubmitted', (data) => {
  console.log('Feedback received:', data);

  // Send to your own analytics
  analytics.track('Feedback Submitted', {
    rating: data.data.rating,
    type: data.data.type,
    page: data.data.metadata.page
  });
});

// Remove event listener
feedbackWidget.off('feedbackSubmitted', callback);

🏗️ Development

Setup

git clone https://github.com/sachin-s-joshi/feedback-widget.git
cd feedback-widget
npm install

Development Server

npm run dev

Visit http://localhost:3000 to see the demo.

Build for Production

# Build NPM package
npm run build:npm

# Build demo
npm run build

Testing

npm test
npm run test:watch

📦 Deployment

As NPM Package

  1. Update version in package.json
  2. Build the package: npm run build:npm
  3. Publish: npm publish

As CDN Asset

  1. Build: npm run build:npm
  2. Upload dist/feedback-widget.min.js to your CDN
  3. Reference in HTML: <script src="your-cdn/feedback-widget.min.js"></script>

Versioning Strategy

// Use semantic versioning
{
  "version": "1.2.3",  // major.minor.patch
  "id": "widget-v1",   // Version-specific widget ID
}

// Deploy multiple versions simultaneously
feedbackWidget.init([
  { id: 'widget-v1', version: '1.0.0', /* config */ },
  { id: 'widget-v2', version: '2.0.0', /* config */ }
]);

🎨 Examples

Feedback Leaf Customization

// Animated circular leaf with custom colors
feedbackWidget.create('animated-leaf', {
  appearance: {
    feedbackLeaf: {
      icon: '🚀',
      shape: 'circle',
      colors: {
        background: 'linear-gradient(45deg, #667eea, #764ba2)',
        text: '#ffffff'
      },
      animation: {
        enabled: true,
        type: 'bounce',
        duration: '2s'
      },
      shadow: {
        enabled: true,
        blur: '20px',
        color: 'rgba(102, 126, 234, 0.4)'
      }
    }
  }
});

// Custom positioned tab with auto-hide
feedbackWidget.create('custom-tab', {
  appearance: {
    feedbackLeaf: {
      text: 'Help us improve!',
      shape: 'rounded',
      size: {
        width: '150px',
        height: '45px'
      },
      positioning: {
        offsetX: '-20px',
        offsetY: '50px'
      },
      hideAfter: 30000  // Hide after 30 seconds
    }
  }
});

// Minimalist icon-only leaf
feedbackWidget.create('icon-only', {
  appearance: {
    feedbackLeaf: {
      icon: '💭',
      shape: 'circle',
      size: {
        width: '50px',
        height: '50px'
      },
      colors: {
        background: '#28a745',
        border: '#20c997'
      },
      animation: {
        enabled: true,
        type: 'pulse',
        duration: '3s'
      }
    }
  }
});

Advanced User Behavior Triggers

// Detect user frustration with rage clicks
feedbackWidget.create('frustration-feedback', {
  triggers: [{
    type: 'rage-click',
    conditions: {
      rageClick: {
        clickThreshold: 3,
        timeWindow: 2000,
        elementSelector: 'button, .clickable', // Target interactive elements
        excludeSelectors: ['.demo-btn', '.nav-btn'] // Exclude navigation
      },
      frequency: 'session'
    }
  }],
  fields: [
    {
      type: 'category',
      label: 'What frustrated you?',
      required: true,
      options: ['Button not working', 'Page loading slow', 'Confusing interface', 'Technical error']
    },
    {
      type: 'text',
      label: 'Tell us more about the problem',
      required: false,
      placeholder: 'What were you trying to do?'
    }
  ],
  appearance: {
    colors: { primary: '#ff6b6b' },
    feedbackLeaf: {
      icon: '😤',
      text: 'Frustrated?',
      animation: { enabled: true, type: 'bounce' }
    }
  }
});

// Detect confused user navigation patterns
feedbackWidget.create('confusion-help', {
  triggers: [{
    type: 'confused-navigation',
    conditions: {
      confusedNavigation: {
        backAndForthThreshold: 2,
        scrollJumpThreshold: 4,
        inactivityThreshold: 8000
      },
      frequency: 'session'
    }
  }],
  fields: [
    {
      type: 'category',
      label: 'Are you having trouble finding something?',
      required: true,
      options: ['Looking for specific info', 'Navigation is confusing', 'Page layout unclear', 'Other']
    },
    {
      type: 'text',
      label: 'How can we help you find what you need?',
      required: false
    }
  ],
  appearance: {
    colors: { primary: '#4ecdc4' },
    feedbackLeaf: {
      icon: '🤔',
      text: 'Need help?'
    }
  }
});

// React to high-value user actions from analytics
feedbackWidget.create('post-purchase-feedback', {
  triggers: [{
    type: 'datalayer-event',
    conditions: {
      dataLayerEvent: {
        eventName: 'purchase',
        eventProperties: {
          value: 100, // Purchases over $100
          event_category: 'ecommerce'
        },
        matchType: 'partial'
      },
      frequency: 'once'
    }
  }],
  fields: [
    {
      type: 'rating',
      label: 'How was your checkout experience?',
      required: true
    },
    {
      type: 'text',
      label: 'How can we improve?',
      required: false
    }
  ]
});

// Multi-trigger combination for comprehensive coverage
feedbackWidget.create('smart-feedback', {
  triggers: [
    // Catch frustrated users
    {
      type: 'rage-click',
      conditions: {
        rageClick: { clickThreshold: 3 },
        frequency: 'session'
      }
    },
    // Catch confused users
    {
      type: 'confused-navigation',
      conditions: {
        confusedNavigation: { backAndForthThreshold: 3 },
        frequency: 'session'
      }
    },
    // Catch engaged users
    {
      type: 'datalayer-event',
      conditions: {
        dataLayerEvent: {
          eventName: 'user_engagement',
          eventProperties: { engagement_level: 'high' }
        },
        frequency: 'session'
      }
    }
  ],
  fields: [
    {
      type: 'category',
      label: 'How can we help improve your experience?',
      required: true,
      options: ['Technical issue', 'Navigation help', 'General feedback', 'Feature request']
    }
  ]
});

E-commerce Product Feedback

feedbackWidget.create('product-feedback', {
  triggers: [
    {
      type: 'page',
      conditions: {
        pages: ['/product/*'],
        frequency: 'once'
      }
    }
  ],
  fields: [
    {
      type: 'rating',
      label: 'Rate this product',
      required: true
    },
    {
      type: 'category',
      label: 'Feedback about',
      required: true,
      options: ['Quality', 'Price', 'Shipping', 'Customer Service']
    },
    {
      type: 'text',
      label: 'Additional comments',
      required: false
    }
  ],
  dataLayer: {
    enabled: true,
    eventName: 'product_feedback',
    customProperties: {
      product_category: 'electronics'
    }
  }
});

Blog Reading Experience

feedbackWidget.create('blog-feedback', {
  triggers: [
    {
      type: 'scroll',
      conditions: {
        scrollPercentage: 90,
        frequency: 'session'
      }
    }
  ],
  fields: [
    {
      type: 'nps',
      label: 'How likely are you to recommend this article?',
      required: true
    },
    {
      type: 'text',
      label: 'What did you think of this article?',
      required: false,
      validation: { maxLength: 300 }
    }
  ],
  appearance: {
    position: 'center',
    colors: {
      primary: '#6f42c1'
    }
  }
});

Customer Support Satisfaction

feedbackWidget.create('support-satisfaction', {
  triggers: [
    {
      type: 'element-click',
      conditions: {
        elementSelector: '.chat-end-button',
        frequency: 'always'
      }
    }
  ],
  fields: [
    {
      type: 'rating',
      label: 'How satisfied were you with our support?',
      required: true
    },
    {
      type: 'category',
      label: 'Issue type',
      required: false,
      options: ['Technical', 'Billing', 'General Question', 'Bug Report']
    },
    {
      type: 'text',
      label: 'How can we improve?',
      required: false
    },
    {
      type: 'email',
      label: 'Follow-up email (optional)',
      required: false
    }
  ]
});

🔒 Privacy & Security

  • All data is processed client-side before transmission
  • No tracking cookies or persistent storage without consent
  • Configurable data retention policies
  • GDPR compliant data handling
  • XSS protection through input sanitization
  • CSP compatible implementation

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Commit changes: git commit -am 'Add my feature'
  4. Push to the branch: git push origin feature/my-feature
  5. Submit a pull request

📄 License

MIT License - see LICENSE file for details.

📋 Changelog

v1.2.2 (Latest)

  • 📖 Updated: Documentation with correct GitHub repository URLs
  • 📧 Updated: Contact email to [email protected]
  • 👤 Updated: Author information in package.json

v1.2.1

  • 🐛 Fixed: Manual trigger widget visibility issue - widgets now properly show/hide
  • 🐛 Fixed: NPS (0-10 scale) rating selection and validation
  • Improved: Sequential star rating behavior (selecting 4 stars fills stars 1-4)
  • 🧪 Added: Comprehensive trigger validation test suite
  • 📖 Updated: Documentation with latest features and fixes

v1.2.0

  • 🚀 New: Advanced triggers (rage-click, confused navigation, dataLayer events)
  • 🎨 New: Custom feedback leaf with animations and positioning
  • New: Modern animated star ratings with golden glow effects
  • 📊 Enhanced: Analytics integration improvements

🆘 Support


Made with ❤️ for better user feedback collection