Drop-in React component. Strict-mode safe, fully typed, no extra deps.
Mount the Usero widget anywhere in your React tree. The component is a no-op until the user opens it, so it does not affect first paint or hydration. Works with React 18 and 19, Vite, CRA, and any bundler that understands ESM.
npm install @usero/sdkimport { UseroFeedbackWidget } from '@usero/sdk/react'
export function App() {
return (
<>
{/* your app */}
<UseroFeedbackWidget clientId='YOUR_CLIENT_ID' />
</>
)
}Replace YOUR_CLIENT_ID with the id from your Usero dashboard.
Built for React
Ships with full TypeScript declarations. Autocomplete on theme, position, plugin options, callbacks.
Compatible with React 17, 18, and 19. The widget treats React as a soft dependency, no version locks.
The panel renders only after the trigger is clicked, so the widget adds no work to your first render pass.
Double-invocation safe. Effects clean up, listeners deregister, nothing leaks between mounts.
Canny embeds a 200kb iframe and a separate auth bridge. Usero is one component, ~12kb gzipped, no iframe.
Verify
Start your dev server and load any page that renders the app.
Look for the feedback bubble in the bottom-right corner.
Click it. The panel should slide open over your UI.
Submit a test message, then open your Usero inbox and confirm the row appears.
Open your browser console and check there are no errors mentioning @usero/sdk.
Troubleshooting
Confirm the clientId prop is the real id from Settings, not the YOUR_CLIENT_ID placeholder. Then check that <UseroFeedbackWidget /> is actually in the rendered tree and not behind an early return.
You mounted the widget in more than one component. Mount it once, near the root, so it stays alone across route changes. Remove the duplicate from any route or layout component.
That is React StrictMode double-invoking effects in development only. The widget is double-mount safe: effects clean up and listeners deregister, so nothing leaks. It mounts once in production.
Check the widget is not inside a parent with display:none, visibility:hidden, or a zero-size container. The launcher is fixed-positioned, so an ancestor with overflow:hidden and a tiny box can clip it.
Make sure you import from @usero/sdk/react, not @usero/sdk. The React entry ships the typed component; the root entry is the vanilla embed.
FAQ
Yes. The component is a function with stable identity and no mutable closure tricks, so the React compiler treats it as memoizable.
About 12kb gzipped for the React build, including the panel UI. The session replay plugin is a separate chunk that only loads when imported.
Yes. CRA, Vite, Parcel, Rspack, Webpack 5, anything that resolves package "exports" works without config.
No. The widget is self-contained. Drop it anywhere in the tree, ideally near the root so it stays mounted across route changes.
Free tier. No credit card. Two-minute install. Cancel by deleting two lines of code.
Install guides