React Components
React Components
Pre-built React components that make it easy to add contextual feedback collection to your app.
Ready to Use
Drop-in feedback widget
Customizable
Theme and style options
TypeScript
Full type safety
Modern React
Hooks and latest patterns
Installation
Install Feedback Widgetbash
npm install @feedbackkit/widgetFeedback Widget
Basic Usage
Add a contextual feedback widget to your app with interactive overlay
import { FeedbackWidget } from '@feedbackkit/widget';
export default function MyApp() {
return (
<div>
<h1>My Website</h1>
<FeedbackWidget
apiKey="feedbackkit_sk_your_api_key_here"
appName="My App"
trigger="floating"
position="bottom-right"
onSuccess={() => {
console.log('Feedback submitted!');
}}
/>
</div>
);
}Custom Feedback Form
Custom Implementation
Build your own feedback form with full control over the UI
import { useState } from 'react';
interface FeedbackFormProps {
onSubmit: (feedback: FeedbackData) => Promise<void>;
onCancel?: () => void;
}
export function CustomFeedbackForm({ onSubmit, onCancel }: FeedbackFormProps) {
const [formData, setFormData] = useState({
type: 'bug' as const,
priority: 'medium' as const,
title: '',
description: '',
email: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
try {
await onSubmit({
...formData,
metadata: {
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
}
});
// Reset form
setFormData({
type: 'bug',
priority: 'medium',
title: '',
description: '',
email: ''
});
} catch (error) {
console.error('Failed to submit feedback:', error);
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-white mb-2">
Feedback Type
</label>
<select
value={formData.type}
onChange={(e) => setFormData(prev => ({ ...prev, type: e.target.value as any }))}
className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
>
<option value="bug">Bug Report</option>
<option value="feature">Feature Request</option>
<option value="error">Error Report</option>
<option value="general">General Feedback</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-white mb-2">
Priority
</label>
<select
value={formData.priority}
onChange={(e) => setFormData(prev => ({ ...prev, priority: e.target.value as any }))}
className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
<option value="critical">Critical</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-white mb-2">
Title
</label>
<input
type="text"
value={formData.title}
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
placeholder="Brief description of the issue"
className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-white mb-2">
Description
</label>
<textarea
value={formData.description}
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
placeholder="Detailed description of the feedback"
rows={4}
className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-white mb-2">
Email (optional)
</label>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
placeholder="your@email.com"
className="w-full p-2 border border-slate-600 rounded bg-slate-700 text-white"
/>
</div>
<div className="flex space-x-3">
<button
type="submit"
disabled={isSubmitting}
className="flex-1 bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700 disabled:opacity-50"
>
{isSubmitting ? 'Submitting...' : 'Submit Feedback'}
</button>
{onCancel && (
<button
type="button"
onClick={onCancel}
className="px-4 py-2 border border-slate-600 text-slate-300 rounded hover:bg-slate-700"
>
Cancel
</button>
)}
</div>
</form>
);
}Feedback Hooks
useFeedbackSubmission
import { useState, useCallback } from 'react';
export function useFeedbackSubmission() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const submitFeedback = useCallback(async (feedbackData: {
type: 'bug' | 'feature' | 'error' | 'general';
priority: 'low' | 'medium' | 'high' | 'critical';
title: string;
description: string;
email?: string;
screenshot?: string;
}) => {
setIsSubmitting(true);
setError(null);
try {
const response = await fetch('https://api.feedbackkit.io/feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.REACT_APP_FEEDBACKKIT_API_KEY,
},
body: JSON.stringify({
...feedbackData,
metadata: {
url: window.location.href,
userAgent: navigator.userAgent,
appName: 'My App',
timestamp: new Date().toISOString(),
},
}),
});
if (!response.ok) {
throw new Error('Failed to submit feedback');
}
const result = await response.json();
return result;
} catch (err: any) {
setError(err.message);
throw err;
} finally {
setIsSubmitting(false);
}
}, []);
return {
submitFeedback,
isSubmitting,
error,
};
}useScreenshotCapture
import { useState, useCallback } from 'react';
export function useScreenshotCapture() {
const [isCapturing, setIsCapturing] = useState(false);
const [screenshot, setScreenshot] = useState<string | null>(null);
const captureScreenshot = useCallback(async () => {
setIsCapturing(true);
try {
// Use html2canvas or similar library
const canvas = await import('html2canvas').then(mod =>
mod.default(document.body)
);
const dataUrl = canvas.toDataURL('image/png');
setScreenshot(dataUrl);
return dataUrl;
} catch (error) {
console.error('Failed to capture screenshot:', error);
throw error;
} finally {
setIsCapturing(false);
}
}, []);
const clearScreenshot = useCallback(() => {
setScreenshot(null);
}, []);
return {
captureScreenshot,
clearScreenshot,
screenshot,
isCapturing,
};
}Performance Optimization
Lazy Loading
import { lazy, Suspense } from 'react';
// Lazy load the feedback widget
const FeedbackWidget = lazy(() =>
import('@feedbackkit/widget').then(mod => ({
default: mod.FeedbackWidget
}))
);
export default function MyApp() {
return (
<div>
<h1>My Website</h1>
<Suspense fallback={<div>Loading feedback widget...</div>}>
<FeedbackWidget
apiKey={process.env.REACT_APP_FEEDBACKKIT_API_KEY}
appName="My App"
/>
</Suspense>
</div>
);
}Memoization
import { useMemo, useCallback } from 'react';
export default function MyApp() {
// Memoize theme configuration
const theme = useMemo(() => ({
primaryColor: "#ec4899",
borderRadius: "8px"
}), []);
// Memoize success callback
const handleSuccess = useCallback((feedback) => {
console.log('Feedback submitted:', feedback);
// Track analytics
analytics.track('feedback_submitted', {
type: feedback.type,
priority: feedback.priority
});
}, []);
return (
<div>
<h1>My Website</h1>
<FeedbackWidget
apiKey={process.env.REACT_APP_FEEDBACKKIT_API_KEY}
appName="My App"
theme={theme}
onSuccess={handleSuccess}
/>
</div>
);
}Best Practices
Error Handling
Always handle errors
Provide fallback UI and user-friendly error messages
Loading states
Show loading indicators during feedback submission
Validation
Validate feedback data before submission
Accessibility
Keyboard navigation
Ensure all feedback forms are keyboard accessible
Screen readers
Add proper ARIA labels and descriptions
Focus management
Manage focus when opening/closing feedback forms