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 ();
For more control, import and initialize the widget manually: import { initWidget } from '@contextcompany/widget' ;
// Initialize the widget
const cleanup = initWidget ({
enabled: true ,
onMount : () => {
console . log ( 'Widget mounted!' );
},
});
// Later, cleanup when needed
cleanup ();
Use the widget in React with proper lifecycle management: import { useEffect } from 'react' ;
import { initWidget } from '@contextcompany/widget' ;
function App () {
useEffect (() => {
const cleanup = initWidget ({
enabled: true ,
onMount : () => {
console . log ( 'Widget ready' );
},
});
return cleanup ;
}, []);
return < div > { /* Your app */ } </ div > ;
}
Initialize the widget in a client component: 'use client' ;
import { useEffect } from 'react' ;
import { initWidget } from '@contextcompany/widget' ;
export default function WidgetProvider () {
useEffect (() => {
const cleanup = initWidget ({ enabled: true });
return cleanup ;
}, []);
return null ;
}
Then include it in your layout: // app/layout.tsx
import WidgetProvider from './widget-provider' ;
export default function RootLayout ({ children }) {
return (
< html >
< body >
{ children }
< WidgetProvider />
</ body >
</ html >
);
}
API Reference
Initializes the feedback widget and mounts it to the page.
function initWidget ( options ?: WidgetOptions ) : (() => void ) | undefined
Configuration options for the widget. Whether the widget should be enabled. Set to false to prevent initialization (useful for conditional loading based on environment).
Callback function called when the widget is successfully mounted to the DOM.
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.
This function is also returned by initWidget for convenience.
Example:
import { cleanup } from '@contextcompany/widget' ;
// Remove the widget
cleanup ();
Configuration object for the widget.
interface WidgetOptions {
enabled ?: boolean ;
onMount ?: () => void ;
}
Controls whether the widget should be initialized. Useful for conditional loading: initWidget ({
enabled: process . env . NODE_ENV === 'production' ,
});
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
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
Check browser console for errors
Verify window object exists (client-side only)
Ensure enabled is not set to false
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