diff --git a/ui/custom-ui/README.md b/ui/custom-ui/README.md
index d613f198..f64b0c23 100644
--- a/ui/custom-ui/README.md
+++ b/ui/custom-ui/README.md
@@ -16,3 +16,4 @@ Build your own standalone chat frontend for Copilot Studio agents.
| [assistant-ui/](./assistant-ui/) | React chat UI using the Assistant UI library |
| [directline-js/](./directline-js/) | Custom chat UI using DirectLine API, no WebChat dependency |
| [reasoning-display/](./reasoning-display/) | Display agent reasoning and citations |
+| [webchat-csat/](./webchat-csat/) | Intercept CSAT card with custom emoji rating component (WebChat) |
diff --git a/ui/custom-ui/webchat-csat/README.md b/ui/custom-ui/webchat-csat/README.md
new file mode 100644
index 00000000..cbb9bd62
--- /dev/null
+++ b/ui/custom-ui/webchat-csat/README.md
@@ -0,0 +1,69 @@
+---
+title: Custom CSAT Rating
+parent: Custom UI
+grand_parent: UI
+nav_order: 6
+---
+
+# Custom CSAT Rating
+
+This sample shows how to intercept Copilot Studio's built-in CSAT (Customer Satisfaction) survey card and replace it with a custom React component, while sending the exact same payload back to the bot so the conversation flow continues normally.
+
+## What it does
+
+1. **Detects** the CSAT Adaptive Card using a heuristic (walks the card tree looking for `Action.Submit` nodes with a `rate` data key)
+2. **Replaces** the default card with a modern emoji-based rating component
+3. **Sends back** the same `{ "rate": "N" }` postBack payload the original card would have sent
+
+The sample includes a **mock Direct Line** so you can open `index.html` in a browser and see the custom CSAT component in action without connecting to a real agent.
+
+## How it works
+
+### CSAT detection heuristic
+
+Copilot Studio's `CSATQuestion` node sends an Adaptive Card with five clickable images, each wired to an `Action.Submit` with data like `{ "rate": "1" }` through `{ "rate": "5" }`. The detection function walks the entire card tree and counts how many `Action.Submit` actions carry a `rate` key. If it finds at least 3, it treats the card as a CSAT survey.
+
+{: .note }
+> This is a heuristic. If your bot sends other Adaptive Cards whose submit actions also use a `rate` field, this function will match those too. Adjust the detection logic to suit your bot.
+
+### Activity middleware
+
+WebChat's `activityMiddleware` intercepts each activity before it is rendered. When the middleware detects a CSAT card, it returns the custom `CSATRatingActivity` React component instead of letting WebChat render the Adaptive Card:
+
+```javascript
+const activityMiddleware =
+ () => next => ({ activity, ...otherArgs }) => {
+ if (isCSATCard(activity)) {
+ return () =>