Skip to main content
The @contextcompany/widget package provides a lightweight, embeddable feedback widget that allows users to submit feedback directly from your application. The widget uses Shadow DOM for style isolation and can be auto-loaded or manually initialized.

Installation

npm install @contextcompany/widget

Quick Start

The widget can be auto-loaded by including it via script tag. It will automatically initialize on page load:
<!-- Add to your HTML -->
<script src="https://unpkg.com/@contextcompany/widget"></script>
The widget will appear automatically and be accessible via window.TCCWidget:
// Cleanup if needed
window.TCCWidget.cleanup();

API Reference

initWidget

Initializes the feedback widget and mounts it to the page.
function initWidget(options?: WidgetOptions): (() => void) | undefined
options
WidgetOptions
Configuration options for the widget.
cleanup
(() => void) | undefined
Returns a cleanup function that removes the widget from the DOM. Returns undefined if the widget was not initialized (e.g., when enabled: false or when window is not available).
Examples:
// Basic initialization
const cleanup = initWidget();

// With options
const cleanup = initWidget({
  enabled: process.env.NODE_ENV === 'production',
  onMount: () => {
    console.log('Widget is ready');
  },
});

// Cleanup when done
if (cleanup) cleanup();

cleanup

Removes the widget from the DOM and cleans up resources.
function cleanup(): void
This function is also returned by initWidget for convenience. Example:
import { cleanup } from '@contextcompany/widget';

// Remove the widget
cleanup();

WidgetOptions

Configuration object for the widget.
interface WidgetOptions {
  enabled?: boolean;
  onMount?: () => void;
}
enabled
boolean
default:true
Controls whether the widget should be initialized. Useful for conditional loading:
initWidget({
  enabled: process.env.NODE_ENV === 'production',
});
onMount
() => void
Callback executed after the widget is mounted to the DOM. Use this for initialization logic or analytics:
initWidget({
  onMount: () => {
    analytics.track('Widget Loaded');
  },
});

Global API (Auto-Loading)

When using the script tag auto-loading method, the widget is available globally:
window.TCCWidget = {
  init: (options?: WidgetOptions) => (() => void) | undefined;
  cleanup: () => void;
}
Example:
<script src="https://unpkg.com/@contextcompany/widget"></script>
<script>
  // Re-initialize with custom options
  window.TCCWidget.cleanup();
  window.TCCWidget.init({
    enabled: true,
    onMount: () => console.log('Reinitialized'),
  });
</script>

Features

Shadow DOM Isolation

The widget uses Shadow DOM for complete style isolation:
  • No CSS conflicts with your application
  • Widget styles are scoped and won’t leak
  • Your application styles won’t affect the widget
  • Consistent appearance across different host environments

Lightweight & Performant

  • Small bundle size
  • Lazy-loaded styles
  • No external dependencies
  • Minimal performance impact

SSR Compatible

The widget safely handles server-side rendering:
// Automatically checks for window
initWidget(); // Safe to call on server (no-op)

Multiple Initialization Prevention

The widget prevents duplicate initialization:
initWidget(); // Initializes
initWidget(); // Logs warning: "Widget already initialized"

Usage Patterns

Conditional Loading

Only load the widget in specific environments:
initWidget({
  enabled: 
    process.env.NODE_ENV === 'production' &&
    !window.location.hostname.includes('staging'),
});

With Feature Flags

Integrate with feature flag systems:
import { initWidget } from '@contextcompany/widget';
import { featureFlags } from './flags';

initWidget({
  enabled: featureFlags.feedbackWidget,
  onMount: () => {
    analytics.track('Feedback Widget Enabled');
  },
});

With User Authentication

Load the widget only for authenticated users:
import { useEffect } from 'react';
import { useAuth } from './auth';
import { initWidget } from '@contextcompany/widget';

function App() {
  const { isAuthenticated } = useAuth();

  useEffect(() => {
    if (!isAuthenticated) return;
    
    const cleanup = initWidget();
    return cleanup;
  }, [isAuthenticated]);

  return <div>{/* app */}</div>;
}

Dynamic Enable/Disable

Toggle the widget based on user actions:
import { useState, useEffect } from 'react';
import { initWidget, cleanup } from '@contextcompany/widget';

function App() {
  const [widgetEnabled, setWidgetEnabled] = useState(false);

  useEffect(() => {
    if (!widgetEnabled) {
      cleanup();
      return;
    }
    
    const cleanupFn = initWidget();
    return cleanupFn;
  }, [widgetEnabled]);

  return (
    <div>
      <button onClick={() => setWidgetEnabled(!widgetEnabled)}>
        {widgetEnabled ? 'Disable' : 'Enable'} Feedback
      </button>
    </div>
  );
}

With Analytics

Track widget usage:
initWidget({
  onMount: () => {
    // Track widget load
    analytics.track('Feedback Widget Loaded', {
      timestamp: new Date(),
      page: window.location.pathname,
    });
  },
});

Styling

The widget comes with built-in styles using Tailwind CSS. Since it uses Shadow DOM, your application styles won’t affect the widget, and the widget styles won’t leak into your application.
The widget’s appearance is controlled internally and cannot be customized via external CSS. This ensures a consistent experience across all implementations.

Browser Support

The widget supports all modern browsers with Shadow DOM support:
  • Chrome 53+
  • Firefox 63+
  • Safari 10.1+
  • Edge 79+

Troubleshooting

Widget not appearing

  1. Check browser console for errors
  2. Verify window object exists (client-side only)
  3. Ensure enabled is not set to false
  4. Check if widget was already initialized

Duplicate initialization warning

// Clear existing widget before reinitializing
cleanup();
const newCleanup = initWidget();

SSR/SSG errors

Ensure the widget is only initialized on the client:
if (typeof window !== 'undefined') {
  initWidget();
}
Or use dynamic imports in Next.js:
import dynamic from 'next/dynamic';

const WidgetProvider = dynamic(
  () => import('./widget-provider'),
  { ssr: false }
);

Next Steps

Submit Feedback API

Programmatically submit feedback

View Feedback

View collected feedback in the dashboard

Build docs developers (and LLMs) love