Feedback Menu
Floating feedback panel with loading, success, and error states, customizable edge, and an optional submit handler.
Four edge placements
Each stage below is a position:relative container using container=\'parent\'. Triggers sit centered on each edge and expand inward.
Percentage offset
offset=\'75%\' positions the trigger three-quarters of the way down the left edge.
Pixel offset
offset={420} positions the trigger exactly 420px from the start of the top edge.
Icon-only, compact
Set iconOnly to render a minimal square trigger with a tooltip label - ideal for dense layouts.
Alignment and collision padding
align=\'start\' anchors the trigger at the offset point instead of centering it. collisionPadding keeps the open panel inside the stage bounds.
Customizable copy and fields
Override feedback types, hide the email field, and customize success and submit labels.
1"use client";23import { Separator } from "@/components/ui/separator";4import { cn } from "@/lib/utils";5import FeedbackMenu, {6 FeedbackFormData,7 FeedbackTypeOption,8} from "@/registry/ui/feedback-menu";9import { Bug, MessageSquareText } from "lucide-react";1011const CUSTOM_TYPES: FeedbackTypeOption[] = [12 { value: "issue", label: "Report an issue", icon: Bug },13 { value: "idea", label: "Share an idea", icon: MessageSquareText },14];1516export function FeedbackMenuExample() {17 const handleSubmit = (data: FeedbackFormData) => {18 console.log("feedback submitted:", data);19 };2021 return (22 <div className="relative w-full bg-background text-foreground">23 <FeedbackMenu24 edge="right"25 offset="50%"26 label="Feedback"27 dot28 onSubmit={handleSubmit}29 />3031 <div className="mx-auto flex max-w-3xl flex-col gap-12 px-6 py-16 sm:py-24">32 <Section33 title="Four edge placements"34 description="Each stage below is a position:relative container using container=\'parent\'. Triggers sit centered on each edge and expand inward."35 >36 <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">37 <Stage label="edge=\'top\'">38 <FeedbackMenu39 edge="top"40 offset="50%"41 container="parent"42 label="Top"43 onSubmit={handleSubmit}44 />45 </Stage>46 <Stage label="edge=\'bottom\'">47 <FeedbackMenu48 edge="bottom"49 offset="50%"50 container="parent"51 label="Bottom"52 onSubmit={handleSubmit}53 />54 </Stage>55 <Stage label="edge=\'left\'">56 <FeedbackMenu57 edge="left"58 offset="50%"59 container="parent"60 label="Left"61 onSubmit={handleSubmit}62 />63 </Stage>64 <Stage label="edge=\'right\'">65 <FeedbackMenu66 edge="right"67 offset="50%"68 container="parent"69 label="Right"70 onSubmit={handleSubmit}71 />72 </Stage>73 </div>74 </Section>7576 <Separator />7778 <Section79 title="Percentage offset"80 description="offset=\'75%\' positions the trigger three-quarters of the way down the left edge."81 >82 <Stage label="edge=\'left\' offset=\'75%\'" className="min-h-[960px]">83 <FeedbackMenu84 edge="left"85 offset="75%"86 container="parent"87 label="Help"88 onSubmit={handleSubmit}89 />90 </Stage>91 </Section>9293 <Section94 title="Pixel offset"95 description="offset={420} positions the trigger exactly 420px from the start of the top edge."96 >97 <Stage label="edge=\'top\' offset={420}">98 <FeedbackMenu99 edge="top"100 offset={420}101 container="parent"102 label="Report"103 badge="1"104 onSubmit={handleSubmit}105 />106 </Stage>107 </Section>108109 <Section110 title="Icon-only, compact"111 description="Set iconOnly to render a minimal square trigger with a tooltip label - ideal for dense layouts."112 >113 <Stage label="iconOnly + badge">114 <FeedbackMenu115 edge="right"116 offset="50%"117 container="parent"118 iconOnly119 label="Feedback"120 badge={3}121 onSubmit={handleSubmit}122 />123 </Stage>124 </Section>125126 <Separator />127128 <Section129 title="Alignment and collision padding"130 description="align=\'start\' anchors the trigger at the offset point instead of centering it. collisionPadding keeps the open panel inside the stage bounds."131 >132 <Stage label="edge=\'right\' offset=\'5%\' align=\'start\'">133 <FeedbackMenu134 edge="right"135 offset="5%"136 align="start"137 collisionPadding={24}138 container="parent"139 label="Align start"140 onSubmit={handleSubmit}141 />142 </Stage>143 </Section>144145 <Separator />146147 <Section148 title="Customizable copy and fields"149 description="Override feedback types, hide the email field, and customize success and submit labels."150 >151 <Stage label="custom types + copy">152 <FeedbackMenu153 edge="left"154 offset="50%"155 container="parent"156 label="Ideas"157 feedbackTypes={CUSTOM_TYPES}158 showEmail={false}159 successTitle="Idea received"160 successDescription="We review every suggestion during planning."161 submitLabel="Send idea"162 submittingLabel="Sending..."163 onSubmit={handleSubmit}164 />165 </Stage>166 </Section>167 </div>168 </div>169 );170}171172function Section({173 title,174 description,175 children,176}: {177 title: string;178 description: string;179 children: React.ReactNode;180}) {181 return (182 <section className="flex flex-col gap-4">183 <div className="flex flex-col gap-1">184 <h2 className="text-sm font-semibold tracking-tight text-foreground">185 {title}186 </h2>187 <p className="text-pretty text-sm leading-relaxed text-muted-foreground">188 {description}189 </p>190 </div>191 {children}192 </section>193 );194}195196// A bounded, position:relative stage used to demonstrate `container="parent"`.197function Stage({198 label,199 children,200 className,201}: {202 label: string;203 children: React.ReactNode;204 className?: string;205}) {206 return (207 <div208 className={cn(209 "relative min-h-[680px] w-full overflow-visible rounded-xl border border-dashed border-border bg-muted/30",210 className,211 )}212 >213 <span className="absolute left-3 top-3 rounded-md bg-background px-2 py-1 text-xs font-medium text-muted-foreground shadow-sm">214 {label}215 </span>216 {children}217 </div>218 );219}
Installation & source
Install via the shadcn CLI or copy the registry files manually.
npx shadcn@latest add @tt-ui/feedback-menu
Props
| Name | Type | Default | Description |
|---|---|---|---|
| edge | "top" | "right" | "bottom" | "left" | right | The edge of the feedback menu |
| offset | number | string | 50% | The offset of the feedback menu |
| align | "start" | "center" | "end" | center | Cross-axis alignment relative to the offset point along the chosen edge |
| collisionPadding | number | 16 | Minimum inset from the viewport or parent bounds when clamping panel position |
| label | string | Feedback | The label of the feedback menu |
| iconOnly | boolean | false | Whether the feedback menu is icon only |
| badge | string | number | undefined | The badge of the feedback menu |
| dot | boolean | false | Whether the feedback menu is a dot |
| defaultOpen | boolean | false | Whether the feedback menu is open by default |
| open | boolean | undefined | Whether the feedback menu is open |
| onOpenChange | () => void | undefined | Callback when the feedback menu is opened or closed |
| onSubmit | () => void | undefined | Callback when the feedback is submitted |
| container | "viewport" | "parent" | viewport | The container of the feedback menu |
| title | string | Share feedback | The title of the feedback menu |
| description | string | Tell us what's working, what's broken, or what you'd love to see next. | The description of the feedback menu |
| infoTooltip | string | undefined | Optional tooltip shown on an info icon beside the panel title |
| feedbackTypes | FeedbackTypeOption[] | undefined | Custom feedback type options shown in the select field |
| showEmail | boolean | true | Whether to show the optional email field |
| successTitle | string | Thanks for your feedback | Heading shown in the success state |
| successDescription | string | We've received your note and truly appreciate you taking the time. | Description shown in the success state |
| submitLabel | string | Submit feedback | Label for the submit button |
| submittingLabel | string | Sending | Label for the submit button while loading |
| maxLength | number | 500 | The maximum length of the feedback message |
| className | string | undefined | The class name of the feedback menu |
| triggerClassName | string | undefined | The class name of the feedback menu trigger |
| panelClassName | string | undefined | The class name of the feedback menu panel |