How to Add a Feedback Widget to Your React App (Under 5 Minutes)
You shipped your React app. Users are using it. But you have no idea what they think. Maybe they love it. Maybe the checkout flow is confusing. Maybe there's a bug on mobile you've never seen.
The fastest way to find out is to add a feedback widget directly inside your app. This guide covers three approaches, from zero-code to fully custom.
Option 1: Drop-in Script Tag (No Code)
The simplest approach. You add a single script tag and you get a floating feedback button that handles everything — the UI, submission, and data collection.
In your index.html (or public/index.html in Create React App):
<!-- Add before </body> -->
<script src="https://fpulse.app/w/YOUR_API_KEY_PREFIX.js" async></script>
That's it. A feedback button appears in the bottom-right corner. Users click it, choose positive or negative sentiment, leave a comment, and submit. The feedback goes straight to your dashboard with full context — what page they were on, their browser, screen size, etc.
Customizing the widget
You can customize colors, position, text, and theme from your project dashboard — no code changes needed. The widget automatically picks up your configuration.
Want to separate development feedback from production? Add ?env=dev to the script URL:
<script src="https://fpulse.app/w/YOUR_PREFIX.js?env=dev" async></script>
Using it with React's public folder
If you're using Create React App, Next.js, or Vite, drop the script tag in your HTML entry point:
- Create React App:
public/index.html - Next.js (Pages Router):
pages/_document.tsxinside<body> - Next.js (App Router):
app/layout.tsxinside<body> - Vite:
index.html
Option 2: REST API with a Custom React Component
If you want full control over the UI, use the REST API directly. This lets you build a feedback component that matches your app's design system.
The API endpoint
POST https://fpulse.app/api/v1/feedback
Headers:
X-API-Key: your_api_key_here
Content-Type: application/json
Body:
{
"sentiment": "positive" | "negative",
"comment": "The new dashboard is great!",
"screen_id": "dashboard",
"app_version": "2.1.0",
"app_type": "web"
}
Only sentiment is required. Everything else is optional but helps you understand context when reviewing feedback later.
Building a feedback component
import { useState } from 'react';
function FeedbackWidget() {
const [sentiment, setSentiment] = useState(null);
const [comment, setComment] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [loading, setLoading] = useState(false);
const submit = async () => {
if (!sentiment || loading) return;
setLoading(true);
try {
const res = await fetch('https://fpulse.app/api/v1/feedback', {
method: 'POST',
headers: {
'X-API-Key': process.env.REACT_APP_FPULSE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
sentiment,
comment: comment || undefined,
screen_id: window.location.pathname,
app_type: 'web',
}),
});
if (res.ok) {
setSubmitted(true);
setTimeout(() => {
setIsOpen(false);
setSubmitted(false);
setSentiment(null);
setComment('');
}, 2000);
}
} catch (err) {
console.error('Feedback submission failed:', err);
} finally {
setLoading(false);
}
};
if (!isOpen) {
return (
<button
onClick={() => setIsOpen(true)}
style={{
position: 'fixed', bottom: 24, right: 24,
background: '#10B981', color: '#fff',
border: 'none', borderRadius: '50%',
width: 56, height: 56, cursor: 'pointer',
fontSize: 24, boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
}}
>
💬
</button>
);
}
return (
<div style={{
position: 'fixed', bottom: 24, right: 24,
background: '#1F2937', borderRadius: 12,
padding: 24, width: 320, color: '#fff',
boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
}}>
{submitted ? (
<p style={{ textAlign: 'center' }}>Thanks for your feedback! ✅</p>
) : (
<>
<p style={{ marginBottom: 16, fontWeight: 600 }}>
How's your experience?
</p>
<div style={{ display: 'flex', gap: 12, marginBottom: 16 }}>
{['positive', 'negative'].map((s) => (
<button key={s} onClick={() => setSentiment(s)}
style={{
flex: 1, padding: '12px', borderRadius: 8,
border: sentiment === s ? '2px solid #10B981' : '2px solid #374151',
background: sentiment === s ? '#10B98120' : '#374151',
cursor: 'pointer', fontSize: 24, color: '#fff',
}}>
{s === 'positive' ? '👍' : '👎'}
</button>
))}
</div>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Tell us more (optional)..."
rows={3}
style={{
width: '100%', background: '#374151', border: '1px solid #4B5563',
borderRadius: 8, padding: 12, color: '#fff', resize: 'none',
marginBottom: 12, boxSizing: 'border-box',
}}
/>
<button onClick={submit} disabled={!sentiment || loading}
style={{
width: '100%', padding: 12, borderRadius: 8,
background: sentiment ? '#10B981' : '#4B5563',
color: '#fff', border: 'none', cursor: sentiment ? 'pointer' : 'not-allowed',
fontWeight: 600,
}}>
{loading ? 'Sending...' : 'Send Feedback'}
</button>
</>
)}
</div>
);
}
export default FeedbackWidget;
Then add it to your app layout:
import FeedbackWidget from './components/FeedbackWidget';
function App() {
return (
<>
{/* Your app content */}
<FeedbackWidget />
</>
);
}
Environment variable setup
Store your API key in environment variables, never in source code:
# .env
REACT_APP_FPULSE_API_KEY=fp_your_api_key_here
For Next.js, prefix with NEXT_PUBLIC_ instead of REACT_APP_.
Option 3: Conditional Rendering Based on User State
A common pattern is showing the feedback widget only to certain users or at specific moments — after completing an action, after X days of usage, or only to logged-in users.
function App() {
const { user } = useAuth();
const [showFeedback, setShowFeedback] = useState(false);
useEffect(() => {
// Show feedback widget after user has been active for 5 minutes
const timer = setTimeout(() => setShowFeedback(true), 5 * 60 * 1000);
return () => clearTimeout(timer);
}, []);
return (
<>
{/* Your app */}
{user && showFeedback && <FeedbackWidget />}
</>
);
}
This approach gives you higher quality feedback because users have actually used the product before being asked.
Which option should you pick?
- Script tag — You want feedback collection running in under a minute. No React code to maintain.
- REST API component — You want the widget to match your design system exactly, or you need custom logic (conditional display, user metadata, etc.).
- Combination — Use the script tag on your marketing site, and the API component inside your authenticated app.
What happens after you collect feedback
This is where it gets interesting. Collecting feedback is step one. Feedback Pulse gives you:
- Sentiment trends — See if your latest release made things better or worse
- Screen-level data — Know exactly which page or feature is causing frustration
- AI action suggestions — Get prioritized recommendations on what to fix first
- Slack and ClickUp integration — Route negative feedback directly to your team
The goal isn't just to collect feedback. It's to close the loop between what users experience and what your team works on next.
Get started free — no credit card required. The free tier includes 1,000 feedback entries per month.