← All proposals

Feedback Bar

A small atomic component: a thumbs-up and thumbs-down pair with one piece of state, built from the local IconButton (its existing isPressed toggle, which maps to aria-pressed).

Net-new componentOpen in Storybook

proposals/FeedbackBar/HANDOFF.md77 lines · View on GitHub

Handoff: FeedbackBar

What and why

Thumbs up and down for rating agent output. The same control belongs in several places (under a chat reply, in a Suggestion Card, under a Chain of Thought or a Code Diff), and without a component each screen builds its own pair with its own labels, pressed state and spacing. It is UI only: it reports up, down or null and does nothing else; what the screen does with the signal (analytics, a reason, a thank-you) is not decided. Built from the local IconButton. Figma: Feedback bar page (the designer's hand-built component set; it replaced the first capture). Context: context.md.

Exploration and decisions

  • Its own atomic component, not part of Chat Message or Suggestion Card, which stay slot-based; the screen places the bar.
  • UI only: no analytics, no reason flow, no copy. The screen sends the choice with the context that makes it useful.
  • Built from the existing IconButton (isPressed maps to aria-pressed); pressing the chosen thumb again clears it. No solid thumb icon exists, so pressed uses the IconButton's pressed styling.
  • One look, the bare thumbs. A bordered prompt-bar look with a dismiss button was built, then removed at the designer's request.
  • No text hardcoded, and wording must not promise an outcome (the team has not decided what feedback is for).
  • statusLabel replaces the thumbs, in Copy XS and the secondary color, in a region that is always in the DOM so it is announced. The thumbs and the status text fade in when they swap (off with prefers-reduced-motion).
  • Top-level in the sidebar, not under AI Chat: nothing in it is chat-specific.

Full list and reasons: decisions.md.

API

FeedbackBar (a div with role='group'; extends div props except onChange):

Prop Type Default Purpose
children ReactNode none Extra actions after the thumbs (for example copy or retry).
className string none Additional class names.
downLabel string none (required) Screen-reader text and tooltip of the thumbs-down button.
groupLabel string none (required) Name of the group, read by screen readers (for example "Rate this response").
onValueChange (value: 'down' | 'up' | null) => void none Called with the new choice; pressing the chosen thumb again passes null.
statusLabel string none Replaces the thumbs with text (for example a thank-you); announced when it appears.
upLabel string none (required) Screen-reader text and tooltip of the thumbs-up button.
value 'down' | 'up' | null null The current choice (controlled).

Variants and states: no choice, thumbs up chosen, thumbs down chosen, status text, with extra actions. Stories: Interactive, AllStates.

Tokens used

  • --pds-animation-duration-reveal
  • --pds-animation-timing-function-default
  • --pds-color-foreground-default
  • --pds-color-foreground-default-secondary
  • --pds-spacing-2xs
  • --pds-spacing-l (story only)

Impact

From impact.md: no existing PDS component is reached and no --pds-* token is redefined. No other proposal imports it by code. SuggestionCard composes it in its stories (in actionsEnd), and the Chat with an agent recipe and Chat Panel's sample content use it; port FeedbackBar before them.

Reused vs net-new

  • Reused: the local IconButton (two toggles, unchanged; port with IconButton and Icon, which are ready), the icons thumbsUp and thumbsDown, the semantic pds-copy-xs class. In stories only: the package ClipboardButton for the copy action.
  • Net-new: .pds-feedback-bar, __actions, __status; all props; one keyframe, pds-feedback-bar-fade-in. No custom CSS variables and no raw values. Details in custom.md.

Accessibility

  • A role='group' named by groupLabel, with two toggle buttons (aria-pressed from isPressed), each labelled by upLabel or downLabel and with a tooltip; keyboard operable and focus styles come from the IconButton.
  • The status text is in a role='status' region that stays in the DOM while empty, so the thank-you is announced when it appears.
  • Motion: the fade is off under prefers-reduced-motion.
  • The a11y panel has not been run, and light and dark were not checked by eye in a session.

Decisions and open questions

For the engineer or designer to settle:

  • What is the signal used for, and who owns it (analytics, ML)? It decides whether a reason after thumbs-down is needed, and whether the screen needs a plain disclosure.
  • A filled (solid) thumb for the pressed state would need a new icon.
  • Should the extra actions (copy, retry) have their own component, a message actions bar?
  • The thumbs use the IconButton at size s; confirm that is the size wanted everywhere.

Port notes

Would live in src/components/ (an agent or conversation category folder, or buttons; generator npm run generate:component). Not yet matching v2: the local mergeClasses (from Button/utils, use @libs/components/utils) and the local IconButton (use the ported one). pds-copy-xs is !important, so a rule on the same element cannot override size or weight.

Preview

npm run storybook, story Component Proposals / Feedback Bar. Figma: https://www.figma.com/design/AMGWJbraJoi2PKWnxo29Ah/PDS-Proposals?node-id=291-6854, page Feedback bar (the designer's component set, not a capture).