Skip to content
txbrendan/ui

Feedback Banner

beta

A banner that asks one question with a few answer buttons, says thanks after an answer, and can be dismissed.

Built from Card and Button.

Preview

How is the new editor?

One click. It shapes what we fix next.

after an answer

Thanks for your feedback

We read every answer, and we will keep improving the editor.

default answers, no icon, not dismissible

Did this answer your question?

three answers, tone neutral

How was setting up your account?

narrow container

How is the new editor?

One click. It shapes what we fix next.

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the block. It also adds every component it uses. Its own files go in your components folder, and they are yours to edit.

    npx shadcn@latest add @txbrendan/feedback-banner

Use it for

  • Asking how a new feature is going, right where people use it.
  • A yes or no question at the end of a help article: "Did this answer your question?".
  • Ask one short question with two or three answers. Send the answer in onValueChange.
  • Save the answer or the dismissal, and stop showing the banner to that person.
  • Write a question that the default Yes and No answer, or pass options with your own labels.

Avoid it for

  • Long surveys or free-text answers. Link to a form instead.
  • Errors, warnings, or news. Use Alert for a whole page, or Notice under the thing it is about.
  • Asking before people have used the thing you ask about.
  • Showing it on every visit. Ask once, then respect the answer or the dismissal.

Props

  • title

    The question, like "Is the new editor working for you?". It also names the answer buttons. Required.

    ReactNode

  • description

    One short line under the question: why you ask, or how long it takes.

    ReactNode

  • options

    The answers, one button each, all the same weight. Each value must be unique.

    { value: string; label: ReactNode; icon?: ReactNode }[] · default [{ value: "yes", label: "Yes" }, { value: "no", label: "No" }]

  • onValueChange

    Called with the option's value when people pick one. Send it to your feedback store here.

    (value: string) => void

  • value

    The picked option's value, when you control it. null shows the question.

    string | null

  • defaultValue

    The picked option's value to start with. Set it to start on the thank-you message.

    string | null · default null

  • icon

    An icon element, like <MessageCircle />. It sits in a round tile and turns into a check after an answer.

    ReactNode

  • tone

    Color of the icon tile.

    "neutral" | "brand" | "danger" | "success" | "warning" · default "neutral"

  • thanksTitle

    Shown in place of the question after an answer.

    ReactNode · default "Thanks for your feedback"

  • thanksDescription

    One line under thanksTitle, like what happens next.

    ReactNode

  • dismissible

    Shows the dismiss button.

    boolean · default true

  • dismissLabel

    The dismiss button's name for screen readers.

    string · default "Dismiss"

  • onDismiss

    Called when people dismiss the banner, after it hides. Save this, so you do not ask again.

    () => void

  • className

    Extra classes on the outer div, which holds the container query.

    string

Accessibility

  • The answer buttons sit in a group named by the question, so each one reads with the question.
  • After an answer, the thank-you message takes the place of the question, and focus moves to it, so screen readers read it.
  • The icon tile is hidden from screen readers. Write a question that makes sense without it.
  • The dismiss button is named by dismissLabel and described by the question or the thanks. Dismissing removes the banner, so focus falls back to the page. If that matters, move focus in onDismiss.

Examples

Ask about a new feature

<FeedbackBanner
  icon={<MessageCircle />}
  tone="brand"
  title="How is the new editor?"
  description="One click. It shapes what we fix next."
  options={[
    { value: "good", label: "Love it", icon: <ThumbsUp /> },
    { value: "bad", label: "Needs work", icon: <ThumbsDown /> },
  ]}
  onValueChange={(answer) => sendFeedback("editor", answer)}
  onDismiss={() => saveSetting("editorFeedbackHidden", true)}
/>

Yes or no, at the end of an article

<FeedbackBanner
  title="Did this answer your question?"
  thanksDescription="We read every answer."
  onValueChange={(answer) => sendFeedback(articleId, answer)}
  dismissible={false}
/>