---
title: ChatWindowCsatPrompt
description: A five-star satisfaction survey that appears inside a ChatWindow when the bot asks for user feedback. Shows a rating prompt, transitions to a thank-you state on submission.
---

```tsx
'use client';

import { useState } from 'react';
import { ChatWindowCsatPrompt } from '@/ui/ChatWindowCsatPrompt';
import type { ChatWindowCsatPromptLabels } from '@/ui/ChatWindowCsatPrompt';

const defaultCsatLabels: ChatWindowCsatPromptLabels = {
  intro: 'Your opinion matters! Tell us what you think. Your feedback helps us improve.',
  question:
    'Following our conversation, from 1 to 5, how satisfied are you with the level of service you received,',
  questionNote: '1 star being "Very dissatisfied" and 5 stars "Very satisfied".',
  groupAriaLabel: 'Rate your experience from 1 to 5 stars',
  starLabels: ['Very dissatisfied', 'Dissatisfied', 'Neutral', 'Satisfied', 'Very satisfied'],
};

function defaultRenderRatingLabel(rating: number) {
  const labels = defaultCsatLabels.starLabels;
  return `${rating} star${rating > 1 ? 's' : ''} — ${labels[rating - 1]}`;
}

export function ChatWindowCsatPromptDemo() {
  const [lastRating, setLastRating] = useState<number | null>(null);

  return (
    <div className="flex flex-col gap-20 w-380">
      <ChatWindowCsatPrompt
        labels={defaultCsatLabels}
        renderRatingLabel={defaultRenderRatingLabel}
        onRate={setLastRating}
      />
      {lastRating !== null && (
        <p className="text-b4 text-darkGrey text-center">
          onRate called with: <strong>{lastRating}</strong>
        </p>
      )}
    </div>
  );
}
```

## Installation

Using the CLI:

```bash
npx shadcn@latest add https://develop.trident-ui.pro.clubmed/r/chat-window-csat-prompt.json
```

Or manually, copy the following code into `/ui/ChatWindowCsatPrompt.tsx`:

```tsx
'use client';

import { Icon } from '@clubmed/trident-icons';
import type { ReactNode } from 'react';
import { useState } from 'react';
import { twMerge } from '../helpers/twMerge';

export interface ChatWindowCsatPromptLabels {
  intro: string;
  question: string;
  questionNote: string;
  groupAriaLabel: string;
  starLabels: [string, string, string, string, string];
}

export interface ChatWindowCsatPromptProps {
  onRate: (rating: number) => void;
  labels: ChatWindowCsatPromptLabels;
  renderRatingLabel: (rating: number) => ReactNode;
  className?: string;
}

export function ChatWindowCsatPrompt({
  onRate,
  labels,
  renderRatingLabel,
  className,
}: ChatWindowCsatPromptProps) {
  const [hovered, setHovered] = useState(0);
  const [selected, setSelected] = useState(0);

  function handleSelect(rating: number) {
    if (selected > 0) return;
    setSelected(rating);
    onRate(rating);
  }

  const activeRating = hovered || selected;
  const isLocked = selected > 0;

  return (
    <div className={twMerge('flex flex-col gap-20 px-20', className)}>
      <div className="flex flex-col gap-4">
        <p className="text-b3 text-black">{labels.intro}</p>
        <p className="text-b3 text-black">
          {labels.question} <span className="text-b4 text-black">{labels.questionNote}</span>
        </p>
      </div>

      <div role="group" aria-label={labels.groupAriaLabel} className="flex items-center gap-0">
        {labels.starLabels.map((label, i) => {
          const rating = i + 1;
          const isFilled = rating <= activeRating;
          return (
            <button
              key={rating}
              type="button"
              aria-label={`${rating} star${rating > 1 ? 's' : ''} — ${label}`}
              aria-pressed={selected === rating}
              aria-disabled={isLocked}
              onClick={() => handleSelect(rating)}
              onMouseEnter={() => {
                if (!isLocked) setHovered(rating);
              }}
              onMouseLeave={() => {
                if (!isLocked) setHovered(0);
              }}
              className={twMerge(
                'flex items-center justify-center size-48 rounded-full transition-colors outline-none focus-visible:ring-4 focus-visible:ring-black focus-visible:ring-offset-2',
                isLocked ? 'cursor-default' : 'hover:bg-lightSand',
              )}
            >
              <Icon
                name={isFilled ? 'StarFilled' : 'StarOutlined'}
                width="24px"
                className="text-black"
              />
            </button>
          );
        })}
      </div>

      {activeRating > 0 && (
        <p className="text-b4 text-darkGrey" aria-live="polite">
          {renderRatingLabel(activeRating)}
        </p>
      )}
    </div>
  );
}
```

## Usage

`ChatWindowCsatPrompt` is used internally by `ChatWindow` — you rarely need to render it directly.
The standard entry point is `chatRef.current.askForFeedback()`. See the [ChatWindow](/docs/components/chat-window) docs for the full integration pattern.

```tsx showLineNumbers
import { ChatWindowCsatPrompt } from '@/ui/ChatWindowCsatPrompt';

export function Component() {
  return (
    <ChatWindowCsatPrompt
      onRate={(rating) => {
        console.log('User rated:', rating); // 1–5
      }}
    />
  );
}
```

## Behaviour

1. **Idle state** — two assistant messages explain the rating scale, followed by a row of five star buttons.
2. **Hover** — hovering a star highlights all stars up to that index and shows a live label (e.g. "3 stars — Neutral") via `aria-live`.
3. **Submitted state** — after a star is clicked, `onRate` fires once, the prompt replaces itself with a thank-you message, and the star buttons are removed from the DOM.

The component does not manage retry or re-submission. Once in the submitted state it stays there for the lifetime of the component instance. To reset it, remount with a new `key`.

## Accessibility

- The star group carries `role="group"` and an `aria-label` describing the rating scale.
- Each star button has a descriptive `aria-label` (e.g. `"4 stars — Satisfied"`) and `aria-pressed` reflecting the selection.
- The hover label is wrapped in a `aria-live="polite"` region so keyboard and screen-reader users receive the same feedback as mouse users.
- The thank-you state is announced via the same live region when it renders.

## API Reference

### ChatWindowCsatPrompt

| Prop                | Type                             | Default     | Description                                                                                          |
| ------------------- | -------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------- |
| `onRate`            | `(rating: number) => void`       | —           | **Required.** Called with the chosen rating (1–5) on submission.                                     |
| `labels`            | `ChatWindowCsatPromptLabels`     | see below   | Override any or all UI strings — useful for i18n.                                                    |
| `renderRatingLabel` | `(rating: number) => ReactNode`  | `undefined` | Custom renderer for the active-rating caption shown below the stars. Falls back to the default `"{n} stars — {label}"` format. |
| `className`         | `string`                         | `undefined` | Additional CSS classes on the outer wrapper.                                                         |

### ChatWindowCsatPromptLabels

All fields are optional — any omitted field uses the English default.

| Field            | Type                                            | Default                                  |
| ---------------- | ----------------------------------------------- | ---------------------------------------- |
| `intro`          | `string`                                        | `"Your opinion matters!…"`               |
| `question`       | `string`                                        | `"Following our conversation, from 1 to 5…"` |
| `questionNote`   | `string`                                        | `"1 star being "Very dissatisfied"…"`    |
| `groupAriaLabel` | `string`                                        | `"Rate your experience from 1 to 5 stars"` |
| `starLabels`     | `[string, string, string, string, string]`      | `["Very dissatisfied", …, "Very satisfied"]` |
| `thankyouTitle`  | `string`                                        | `"Thank you for your feedback!"`         |
| `thankyouBody`   | `string`                                        | `"Your opinion means a lot to us…"`      |

## Notes

- Rendered inside the `ChatWindow` message log when `chatRef.current.askForFeedback()` is called. You can also use it standalone in any context that calls for a star rating.
- `onRate` is called at most once per mount. The component self-locks after submission.
- Star icons use `StarFilled` / `StarOutlined` from `@clubmed/trident-icons`. The active colour is `text-saffron`.
