User feedback
Collect topic, rating, and comment feedback with the drop-in widget or provider-optional hooks.
User feedback is more useful when it is not anonymous.
Cossistant feedback can use the same visitor context as SupportProvider. When
a visitor leaves a rating, topic, or comment, the submission can be attached to
the visitor, and to the contact when that visitor is identified. That means your
team can understand who was blocked, see the context around the feedback, and
follow up in a conversation instead of reading a detached survey response.
Tell users when feedback is associated with their account or support history. Collect only the metadata your team needs, and apply your own consent, retention, and deletion requirements.
Use the default <Feedback /> widget when you want the fastest path. Use
useFeedbackForm when you want the same feedback engine inside your own shadcn
UI. For fully custom feedback surfaces, pass an explicit client, visitorId,
and optional contactId instead of wrapping that subtree in SupportProvider.
Before you start
For the drop-in <Feedback /> widget, complete the
React quickstart first. Your app
should already have:
@cossistant/reactinstalledSupportProviderconfigured with your public key- one Cossistant CSS entrypoint imported at your app root
import { SupportProvider } from "@cossistant/react";
import type { ReactNode } from "react";
import "@cossistant/react/styles.css";
export function AppRoot({ children }: { children: ReactNode }) {
return <SupportProvider publicKey="pk_test_xxxx">{children}</SupportProvider>;
}SupportProvider gives the default widget its client, website, visitor, and
contact context. The feedback hooks can also read that context, but it is a
fallback: when you pass an explicit client, you can use them outside
SupportProvider.
Fastest path
Render the default widget when you want a complete feedback popover without owning the UI.
import { Feedback } from "@cossistant/react";
export function App() {
return (
<Feedback
topics={["Bug", "Feature request", "UX", "Other"]}
trigger="product_feedback"
/>
);
}The trigger value is your label for why this feedback was collected. Use names
like product_feedback, churn, nps_survey, or
conversation_resolved.
Install the shadcn examples
The examples below use Cossistant for feedback state and shadcn components for the interface.
If Cossistant is not installed yet:
pnpm add @cossistant/react
Provider-free examples that create a client directly also need the core package:
pnpm add @cossistant/core
Install the shadcn components used by the examples:
pnpm dlx shadcn@latest add button popover select textarea toggle-group
Then open the Code tab on either preview and copy the component into your
app. Both examples import useFeedbackForm from @cossistant/react/feedback,
so they can submit through the same visitor and contact context as your support
widget.
Emoji feedback
Start with a compact popover: topic, comment, rating, and a small send button.
Star feedback
The same hook can power a five-star rating UI.
Build your own
Use useFeedbackForm when you want full control over the UI but do not want to
rebuild form state, validation, or submission plumbing. Omit client to use the
nearest SupportProvider, or pass client, visitorId, and optional
contactId for provider-free usage.
"use client";
import { useFeedbackForm } from "@cossistant/react/hooks/use-feedback-form";
export function ProductFeedback() {
const feedback = useFeedbackForm({
topics: ["Bug", "Feature request", "UX", "Other"],
trigger: "product_feedback",
commentRequired: true,
});
return (
<form aria-describedby="feedback-status" onSubmit={feedback.handleSubmit}>
<label htmlFor="feedback-topic">Topic</label>
<select
aria-describedby={
feedback.fields.topic.isMissing ? "feedback-topic-error" : undefined
}
aria-invalid={feedback.fields.topic.isMissing}
id="feedback-topic"
onBlur={feedback.fields.topic.handleBlur}
onChange={(event) => feedback.handleTopicChange(event.target.value)}
value={feedback.topic}
>
<option value="">Select topic</option>
{feedback.availableTopics.map((topic) => (
<option key={topic} value={topic}>
{topic}
</option>
))}
</select>
{feedback.fields.topic.isMissing ? (
<p id="feedback-topic-error" role="alert">
Choose a topic.
</p>
) : null}
<label htmlFor="feedback-comment">Comment</label>
<textarea
aria-describedby={
feedback.fields.comment.isMissing
? "feedback-comment-error"
: undefined
}
aria-invalid={feedback.fields.comment.isMissing}
id="feedback-comment"
onBlur={feedback.fields.comment.handleBlur}
onChange={(event) => feedback.handleCommentChange(event.target.value)}
value={feedback.comment}
/>
{feedback.fields.comment.isMissing ? (
<p id="feedback-comment-error" role="alert">
Add a comment.
</p>
) : null}
<fieldset>
<legend>Rating</legend>
{[1, 2, 3, 4, 5].map((rating) => (
<button
aria-pressed={feedback.rating === rating}
key={rating}
onClick={() => feedback.handleRatingSelect(rating)}
type="button"
>
{rating}
</button>
))}
</fieldset>
<button disabled={feedback.submit.disabled} type="submit">
{feedback.submit.label}
</button>
<p aria-live="polite" id="feedback-status">
{feedback.submitted
? "Feedback sent."
: feedback.error?.message ?? ""}
</p>
</form>
);
}useFeedbackForm manages the moving pieces that every feedback UI needs:
- open state with
open,setOpen, andhandleOpenChange - rating state with
rating,hoveredRating,handleRatingSelect, andhandleRatingHoverChange - topic and comment state with normalized values
- validation state for required rating, topics, and comments
- pending, error, and submitted states
- success actions with
sendAnotheranddone
The most common options are:
client: explicitCossistantClientfor provider-free feedbacktopics: the selectable feedback categoriesdefaultTopic: a preselected topic fromtopicstrigger: the reason this feedback form appearedsource: where this feedback came from, defaults towidgetconversationId: attach the feedback to a specific support conversationvisitorId: required when no provider context supplies a visitorcontactId: attach feedback to an identified contactcommentRequired: require a written comment before submitdefaultOpen: start the popover or dialog openonSuccess: run code after Cossistant stores the feedbackonError: handle a failed submission
Provider-free feedback form
"use client";
import { CossistantClient } from "@cossistant/core";
import { useFeedbackForm } from "@cossistant/react/hooks/use-feedback-form";
const client = new CossistantClient({ publicKey: "pk_test_xxxx" });
export function ProviderFreeFeedback({ visitorId }: { visitorId: string }) {
const feedback = useFeedbackForm({
client,
visitorId,
source: "headless",
topics: ["Bug", "Feature request", "UX", "Other"],
});
return (
<form onSubmit={feedback.handleSubmit}>
<button onClick={() => feedback.handleRatingSelect(5)} type="button">
Great
</button>
<button disabled={feedback.submit.disabled} type="submit">
{feedback.submit.label}
</button>
</form>
);
}Lower-level submit API
Use useSubmitFeedback when you already own the entire form and only need the
mutation. It follows the same provider-optional rule: omit client to use
SupportProvider, or pass explicit runtime inputs for a headless form.
"use client";
import { useSubmitFeedback } from "@cossistant/react/hooks/use-submit-feedback";
export function CustomFeedbackSubmit() {
const feedback = useSubmitFeedback({});
async function submit() {
await feedback.mutateAsync({
rating: 5,
topic: "UX",
comment: "The new onboarding screen is much clearer.",
trigger: "onboarding_feedback",
});
}
return (
<button disabled={feedback.isPending} onClick={submit} type="button">
Send feedback
</button>
);
}When client is omitted, useSubmitFeedback reads the Cossistant client,
visitor, website, and contact context from SupportProvider. In most widget
apps, pass rating, topic, comment, trigger, and optionally
conversationId. The hook fills visitorId and contactId from context.
For provider-free forms, pass a client and visitor explicitly:
"use client";
import { CossistantClient } from "@cossistant/core";
import { useSubmitFeedback } from "@cossistant/react/hooks/use-submit-feedback";
const client = new CossistantClient({ publicKey: "pk_test_xxxx" });
export function HeadlessFeedbackSubmit({ visitorId }: { visitorId: string }) {
const feedback = useSubmitFeedback({ client });
return (
<button
disabled={feedback.isPending}
onClick={() =>
feedback.mutate({
rating: 5,
source: "headless",
visitorId,
})
}
type="button"
>
Send feedback
</button>
);
}Only pass visitorId or contactId manually if you are building a lower-level
integration and you know you need to override the context.
Feedback data
Every submission stores:
rating: required, from 1 to 5topic: optional structured categorycomment: optional written feedbacktrigger: optional label for what prompted the formsource: defaults towidgetconversationId: optional conversation linkvisitorId: the visitor who left the feedbackcontactId: the identified contact, when the visitor has one
That association is the important part. A low rating from a signed-in customer can become a real support follow-up, not just a number in a dashboard.
Verify and troubleshoot
After submitting, wait for the form's submitted state and confirm that the
feedback appears in the dashboard with the expected visitor/contact context. A
401 usually means the public key is missing or invalid. A 403 usually means
the current hostname is not allowed. If a provider-free form has no visitor,
pass a valid visitorId; otherwise keep the form inside SupportProvider.
Hook exports
useFeedbackForm and useSubmitFeedback are exported from
@cossistant/react/hooks, @cossistant/react/feedback, and explicit deep
imports like @cossistant/react/hooks/use-feedback-form. Next.js apps can use
the matching @cossistant/next/hooks and @cossistant/next/feedback exports.
Provider-optional hooks treat client carefully: omitting it means "read
provider context", while passing client: null intentionally disables provider
fallback.
Type reference
Feedback props
Prop
Type
useFeedbackForm options
Prop
Type
useFeedbackForm result
Name
Type
Submit feedback variables
Parameter
Type
Was this page helpful?
Open a prefilled documentation issue so the team can act on your feedback.
On this page
Before you startFastest pathInstall the shadcn examplesEmoji feedbackStar feedbackBuild your ownProvider-free feedback formLower-level submit APIFeedback dataVerify and troubleshootHook exportsType referenceFeedback propsuseFeedbackForm optionsuseFeedbackForm resultSubmit feedback variables