LLM index: /llms.txt

Feedback Bar

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.

Is this response helpful?
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-bar
pnpm dlx shadcn@latest add @nexus-ui/feedback-bar
yarn dlx shadcn@latest add @nexus-ui/feedback-bar
bunx shadcn@latest add @nexus-ui/feedback-bar

Install the following dependencies:

npx shadcn@latest add tooltip kbd && npm install @radix-ui/react-slot
pnpm dlx shadcn@latest add tooltip kbd && pnpm add @radix-ui/react-slot
yarn dlx shadcn@latest add tooltip kbd && yarn add @radix-ui/react-slot
bunx shadcn@latest add tooltip kbd && bun add @radix-ui/react-slot

Copy and paste the following code into your project.

components/nexus-ui/feedback-bar.tsx
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

View as markdown Edit on GitHub