LLM index: /llms.txt
Composable feedback bar for both individual AI responses and full conversation rating flows, with flexible action and close slots for thumbs, quick sentiment, or custom controls.
import { HugeiconsIcon } from "@hugeicons/react";
import {
ThumbsUpIcon,
ThumbsDownIcon,
InformationCircleIcon,
Cancel01Icon,
} from "@hugeicons/core-free-icons";
import { Button } from "@/components/ui/button";
import {
FeedbackBar,
FeedbackBarAction,
FeedbackBarActions,
FeedbackBarClose,
FeedbackBarContent,
FeedbackBarLabel,
FeedbackBarPrompt,
} from "@/components/nexus-ui/feedback-bar";
function FeedbackBarDefault() {
return (
<FeedbackBar>
<FeedbackBarContent>
<FeedbackBarPrompt>
<HugeiconsIcon
icon={InformationCircleIcon}
strokeWidth={2.0}
className="size-4 shrink-0"
/>
<FeedbackBarLabel>Is this response helpful?</FeedbackBarLabel>
</FeedbackBarPrompt>
<FeedbackBarActions>
<FeedbackBarAction asChild>
<Button
type="button"
variant="ghost"
size="icon-sm"
className="cursor-pointer rounded-full bg-transparent transition-all active:scale-97"
aria-label="Helpful"
>
<HugeiconsIcon
icon={ThumbsUpIcon}
strokeWidth={2.0}
className="size-4 shrink-0"
/>
</Button>
</FeedbackBarAction>
<FeedbackBarAction asChild>
<Button
type="button"
variant="ghost"
size="icon-sm"
className="cursor-pointer rounded-full bg-transparent transition-all active:scale-97"
aria-label="Not helpful"
>
<HugeiconsIcon
icon={ThumbsDownIcon}
strokeWidth={2.0}
className="size-4 shrink-0"
/>
</Button>
</FeedbackBarAction>
</FeedbackBarActions>
</FeedbackBarContent>
<FeedbackBarClose>
<Button
type="button"
variant="ghost"
size="icon-sm"
className="cursor-pointer rounded-full bg-transparent transition-all active:scale-97"
aria-label="Close"
>
<HugeiconsIcon
icon={Cancel01Icon}
strokeWidth={2.0}
className="size-4 shrink-0"
/>
</Button>
</FeedbackBarClose>
</FeedbackBar>
);
}
export default FeedbackBarDefault;
Installation
npx shadcn@latest add @nexus-ui/feedback-barpnpm dlx shadcn@latest add @nexus-ui/feedback-baryarn dlx shadcn@latest add @nexus-ui/feedback-barbunx shadcn@latest add @nexus-ui/feedback-barInstall the following dependencies:
npx shadcn@latest add tooltip kbd && npm install @radix-ui/react-slotpnpm dlx shadcn@latest add tooltip kbd && pnpm add @radix-ui/react-slotyarn dlx shadcn@latest add tooltip kbd && yarn add @radix-ui/react-slotbunx shadcn@latest add tooltip kbd && bun add @radix-ui/react-slotCopy and paste the following code into your project.
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cn } from "@/lib/utils";
import { Kbd } from "@/components/ui/kbd";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
type FeedbackBarTooltip =
| string
| {
content?: string;
side?: "top" | "right" | "bottom" | "left";
shortcut?: string;
};
type FeedbackBarProps = React.HTMLAttributes<HTMLDivElement>;
function FeedbackBar({ className, ...props }: FeedbackBarProps) {
return (
<div
data-slot="feedback-bar"
role="group"
aria-label="Feedback bar"
className={cn(
"inline-flex h-12 w-full max-w-110 rounded-xl border dark:border-accent bg-card text-sm",
className,
)}
{...props}
/>
);
}
type FeedbackBarContentProps = React.HTMLAttributes<HTMLDivElement>;
function FeedbackBarContent({ className, ...props }: FeedbackBarContentProps) {
return (
<div
data-slot="feedback-bar-content"
className={cn("flex w-full items-center justify-between", className)}
{...props}
/>
);
}
type FeedbackBarPromptProps = React.HTMLAttributes<HTMLDivElement>;
function FeedbackBarPrompt({ className, ...props }: FeedbackBarPromptProps) {
return (
<div
data-slot="feedback-bar-prompt"
className={cn(
"flex h-full w-full items-center justify-start gap-2.5 pl-4",
className,
)}
{...props}
/>
);
}
type FeedbackBarLabelProps = React.HTMLAttributes<HTMLSpanElement>;
function FeedbackBarLabel({ className, ...props }: FeedbackBarLabelProps) {
return (
<span
data-slot="feedback-bar-label"
className={cn("text-sm font-[350] text-foreground", className)}
{...props}
/>
);
}
type FeedbackBarActionsProps = React.HTMLAttributes<HTMLDivElement>;
function FeedbackBarActions({ className, ...props }: FeedbackBarActionsProps) {
return (
<div
data-slot="feedback-bar-actions"
className={cn(
"flex h-full items-center justify-center gap-1.5 px-2",
className,
)}
{...props}
/>
);
}
type FeedbackBarActionProps = React.HTMLAttributes<HTMLDivElement> & {
asChild?: boolean;
tooltip?: FeedbackBarTooltip;
};
function FeedbackBarAction({
asChild = false,
tooltip,
className,
...props
}: FeedbackBarActionProps) {
const Comp = asChild ? Slot : "div";
const { content, side, shortcut } =
typeof tooltip === "string" ? { content: tooltip } : (tooltip ?? {});
if (!content) {
return (
<Comp
data-slot="feedback-bar-action"
className={cn(className)}
{...props}
/>
);
}
return (
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger asChild>
<Comp
data-slot="feedback-bar-action"
className={cn(className)}
{...props}
/>
</TooltipTrigger>
<TooltipContent className="rounded-full" side={side}>
{content}
{shortcut ? <Kbd className="rounded-md!">{shortcut}</Kbd> : null}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
type FeedbackBarCloseProps = React.HTMLAttributes<HTMLDivElement> & {
tooltip?: FeedbackBarTooltip;
};
function FeedbackBarClose({
className,
children,
...props
}: FeedbackBarCloseProps) {
return (
<FeedbackBarAction
asChild
className={cn(
"flex h-full items-center justify-center border-l dark:border-accent px-2",
className,
)}
{...props}
>
<div data-slot="feedback-bar-close">{children}</div>
</FeedbackBarAction>
);
}
export {
FeedbackBar,
FeedbackBarContent,
FeedbackBarPrompt,
FeedbackBarLabel,
FeedbackBarActions,
FeedbackBarAction,
FeedbackBarClose,
};
Update import paths to match your project setup.
Usage
import {
FeedbackBar,
FeedbackBarContent,
FeedbackBarPrompt,
FeedbackBarLabel,
FeedbackBarActions,
FeedbackBarAction,
FeedbackBarClose,
} from "@/components/nexus-ui/feedback-bar";<FeedbackBar>
<FeedbackBarContent>
<FeedbackBarPrompt>
<InfoIcon />
<FeedbackBarLabel>Is this helpful?</FeedbackBarLabel>
</FeedbackBarPrompt>
<FeedbackBarActions>
<FeedbackBarAction asChild>
<Button type="button">Like</Button>
</FeedbackBarAction>
</FeedbackBarActions>
</FeedbackBarContent>
<FeedbackBarClose>
<Button type="button">Close</Button>
</FeedbackBarClose>
</FeedbackBar>Examples
Icon-only Variant
Use icon-only actions with a close control for tight layouts and quick response feedback.
import { HugeiconsIcon } from "@hugeicons/react";
import {
ThumbsUpIcon,
ThumbsDownIcon,
Cancel01Icon,
} from "@hugeicons/core-free-icons";
import { Button } from "@/components/ui/button";
import {
FeedbackBar,
FeedbackBarAction,
FeedbackBarActions,
FeedbackBarClose,
FeedbackBarContent,
} from "@/components/nexus-ui/feedback-bar";
function FeedbackBarCompactMinimal() {
return (
<FeedbackBar className="w-auto">
<FeedbackBarContent>
<FeedbackBarActions>
<FeedbackBarAction asChild tooltip="Helpful">
<Button
type="button"
variant="ghost"
size="icon-sm"
className="cursor-pointer rounded-full bg-transparent transition-all active:scale-97"
aria-label="Helpful"
>
<HugeiconsIcon
icon={ThumbsUpIcon}
strokeWidth={2.0}
className="size-4 shrink-0"
/>
</Button>
</FeedbackBarAction>
<FeedbackBarAction asChild tooltip="Not helpful">
<Button
type="button"
variant="ghost"
size="icon-sm"
className="cursor-pointer rounded-full bg-transparent transition-all active:scale-97"
aria-label="Not helpful"
>
<HugeiconsIcon
icon={ThumbsDownIcon}
strokeWidth={2.0}
className="size-4 shrink-0"
/>
</Button>
</FeedbackBarAction>
</FeedbackBarActions>
</FeedbackBarContent>
<FeedbackBarClose tooltip="Close">
<Button
type="button"
variant="ghost"
size="icon-sm"
className="cursor-pointer rounded-full bg-transparent transition-all active:scale-97"
aria-label="Close"
>
<HugeiconsIcon
icon={Cancel01Icon}
strokeWidth={2.0}
className="size-4 shrink-0"
/>
</Button>
</FeedbackBarClose>
</FeedbackBar>
);
}
export default FeedbackBarCompactMinimal;
Vercel AI SDK Integration
FeedbackBar is intentionally neutral, so you can place it at two different feedback scopes:
- Response-based feedback: capture sentiment for one assistant message.
- Conversation-based feedback: capture sentiment for the entire chat.
Response-based feedback
Attach feedback to a specific assistant message id.
"use client";
import { useState } from "react";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { HugeiconsIcon } from "@hugeicons/react";
import { ThumbsUpIcon, ThumbsDownIcon } from "@hugeicons/core-free-icons";
import { Button } from "@/components/ui/button";
import {
FeedbackBar,
FeedbackBarAction,
FeedbackBarActions,
FeedbackBarContent,
FeedbackBarLabel,
FeedbackBarPrompt,
} from "@/components/nexus-ui/feedback-bar";
type Vote = "up" | "down";
export default function MessageFeedback() {
const { messages } = useChat({
transport: new DefaultChatTransport({ api: "/api/chat" }),
});
const [votes, setVotes] = useState<Record<string, Vote>>({});
async function submitMessageFeedback(messageId: string, vote: Vote) {
setVotes((prev) => ({ ...prev, [messageId]: vote }));
await fetch("/api/feedback/message", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messageId, vote }),
});
}
return (
<div className="flex flex-col gap-4">
{messages
.filter((m) => m.role === "assistant")
.map((m) => (
<FeedbackBar key={m.id}>
<FeedbackBarContent>
<FeedbackBarPrompt>
<FeedbackBarLabel>
{votes[m.id] ? "Thanks for your feedback." : "Was this response helpful?"}
</FeedbackBarLabel>
</FeedbackBarPrompt>
<FeedbackBarActions>
<FeedbackBarAction asChild>
<Button
type="button"
variant={votes[m.id] === "up" ? "secondary" : "ghost"}
size="icon-sm"
onClick={() => submitMessageFeedback(m.id, "up")}
aria-label="Helpful"
>
<HugeiconsIcon icon={ThumbsUpIcon} strokeWidth={2.0} className="size-4" />
</Button>
</FeedbackBarAction>
<FeedbackBarAction asChild>
<Button
type="button"
variant={votes[m.id] === "down" ? "secondary" : "ghost"}
size="icon-sm"
onClick={() => submitMessageFeedback(m.id, "down")}
aria-label="Not helpful"
>
<HugeiconsIcon icon={ThumbsDownIcon} strokeWidth={2.0} className="size-4" />
</Button>
</FeedbackBarAction>
</FeedbackBarActions>
</FeedbackBarContent>
</FeedbackBar>
))}
</div>
);
}Conversation-based feedback
Show one feedback bar for the whole conversation (for example at the end of the thread).
"use client";
import { useState } from "react";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { HugeiconsIcon } from "@hugeicons/react";
import { ThumbsUpIcon, ThumbsDownIcon } from "@hugeicons/core-free-icons";
import { Button } from "@/components/ui/button";
import {
FeedbackBar,
FeedbackBarAction,
FeedbackBarActions,
FeedbackBarContent,
FeedbackBarLabel,
FeedbackBarPrompt,
} from "@/components/nexus-ui/feedback-bar";
type Vote = "up" | "down";
export default function ConversationFeedback() {
const { messages } = useChat({
transport: new DefaultChatTransport({ api: "/api/chat" }),
});
const conversationId = "replace-with-your-session-id";
const [vote, setVote] = useState<Vote | null>(null);
const hasAssistantReply = messages.some((m) => m.role === "assistant");
if (!hasAssistantReply) return null;
async function submitConversationFeedback(nextVote: Vote) {
setVote(nextVote);
await fetch("/api/feedback/conversation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ conversationId, vote: nextVote }),
});
}
return (
<FeedbackBar>
<FeedbackBarContent>
<FeedbackBarPrompt>
<FeedbackBarLabel>
{vote ? "Thanks for rating this conversation." : "How was this conversation?"}
</FeedbackBarLabel>
</FeedbackBarPrompt>
<FeedbackBarActions>
<FeedbackBarAction asChild>
<Button
type="button"
variant={vote === "up" ? "secondary" : "ghost"}
size="icon-sm"
onClick={() => submitConversationFeedback("up")}
aria-label="Good conversation"
>
<HugeiconsIcon icon={ThumbsUpIcon} strokeWidth={2.0} className="size-4" />
</Button>
</FeedbackBarAction>
<FeedbackBarAction asChild>
<Button
type="button"
variant={vote === "down" ? "secondary" : "ghost"}
size="icon-sm"
onClick={() => submitConversationFeedback("down")}
aria-label="Poor conversation"
>
<HugeiconsIcon icon={ThumbsDownIcon} strokeWidth={2.0} className="size-4" />
</Button>
</FeedbackBarAction>
</FeedbackBarActions>
</FeedbackBarContent>
</FeedbackBar>
);
}API Reference
FeedbackBar
Root container for the feedback row.
Prop
Type
FeedbackBarContent
Main area that holds info and actions.
Prop
Type
FeedbackBarPrompt
Left-side info section for icon and question copy.
Prop
Type
FeedbackBarLabel
Text label for the feedback prompt.
Prop
Type
FeedbackBarActions
Container for action controls.
Prop
Type
FeedbackBarAction
Wrapper for a single action item. Use asChild to render your own interactive control directly (for example, a Button). Supports built-in tooltips via tooltip as either a string or object (content, optional side, optional shortcut).
Prop
Type
FeedbackBarClose
Right-side close section with a left divider.
Prop
Type