react-feedback-surveys 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,28 +9,36 @@
9
9
 
10
10
  > Lightweight, customizable survey widgets to collect user feedback in React apps.
11
11
 
12
+ ## Introduction
13
+
14
+ react-feedback-surveys is a standalone, open-source (MIT) UI library — no backend, no hosting, no
15
+ account required. Render the components, wire `onScoreSubmit`/`onFeedbackSubmit` to your own backend
16
+ (or nowhere at all).
17
+
18
+ Want AI-generated insights, a hosted dashboard, and response storage without building your own backend?
19
+ Check out [feedback.tools](https://feedback.tools) — built by the same team.
20
+
12
21
  ## Table of Contents
13
22
 
23
+ - [Introduction](#introduction)
14
24
  - [Features](#features)
15
25
  - [Survey Types](#survey-types)
16
26
  - [Installation](#installation)
17
27
  * [1. Install](#1-install)
18
28
  * [2. Styles](#2-styles)
19
- - [Survey Components](#survey-components)
20
- * [CSAT5 (Customer Satisfaction Score, 5-Point Scale)](#csat5-customer-satisfaction-score-5-point-scale)
21
- * [CSAT2 (Customer Satisfaction Score, 2-Point Scale)](#csat2-customer-satisfaction-score-2-point-scale)
22
- * [NPS10 (Net Promoter Score, 0–10 Scale)](#nps10-net-promoter-score-010-scale)
23
- * [CES7 (Customer Effort Score, 7-Point Scale)](#ces7-customer-effort-score-7-point-scale)
29
+ - [Survey Component](#survey-component)
30
+ * [CSAT (Customer Satisfaction Score)](#csat-customer-satisfaction-score)
31
+ * [NPS (Net Promoter Score)](#nps-net-promoter-score)
32
+ * [CES (Customer Effort Score)](#ces-customer-effort-score)
24
33
  - [Layout Components](#layout-components)
25
34
  * [Popup](#popup)
26
35
  * [Surface](#surface)
27
36
  - [Props](#props)
28
37
  * [Shared Props](#shared-props)
38
+ * [Accessibility Labels](#accessibility-labels)
39
+ * [Attachments](#attachments)
40
+ * [Format Props](#format-props)
29
41
  * [Scale Style Options](#scale-style-options)
30
- + [CSAT2Survey](#csat2survey)
31
- + [CSAT5Survey](#csat5survey)
32
- + [CES7Survey](#ces7survey)
33
- + [NPS10Survey](#nps10survey)
34
42
  - [Styling](#styling)
35
43
  * [CSS Variables](#css-variables)
36
44
  * [Custom Classes](#custom-classes)
@@ -47,20 +55,25 @@
47
55
 
48
56
  ## Features
49
57
 
50
- - **Ready-to-use survey widgets** – CSAT (2 or 5 points), CES (7 points), NPS (0–10)
58
+ - **A single `Survey` component** – the format (methodology, scale length, visual style) is configuration, not a different import
59
+ - **Ready-to-use survey formats** – CSAT (2 or 5 points), CES (7 points), NPS (0–10)
51
60
  - **Multiple scale styles** – emoji, stars, numbers, thumbs
52
61
  - **Flexible placement** – embed inline or display as popup overlay
53
62
  - **Follow-up feedback** – optional text input or multiple choice responses
54
63
  - **Optional email collection** – close the loop by capturing a respondent's email when no user identity is known
64
+ - **Optional screenshot attachments** – let respondents attach a screenshot, captured by a function you provide
55
65
  - **Fully customizable** – CSS variables and custom class names
56
66
  - **Zero dependencies**
57
67
  - **TypeScript support**
58
68
 
59
69
  ## Survey Types
60
70
 
61
- - **CSAT (Customer Satisfaction Score):** 2-point (`csat2`) or 5-point (`csat5`) scales
62
- - **NPS (Net Promoter Score):** 0–10 numeric scale (`nps10`)
63
- - **CES (Customer Effort Score):** 7-point numeric scale (`ces7`)
71
+ `Survey` covers four feedback methodologies, selected with the `type` prop. `CSAT` is the only one with a variable scale length, set via `points`:
72
+
73
+ - **CSAT (Customer Satisfaction Score):** `type="csat"`, 2-point or 5-point scale (`points={2}` or `points={5}`)
74
+ - **NPS (Net Promoter Score):** `type="nps"`, fixed 0–10 scale
75
+ - **CES (Customer Effort Score):** `type="ces"`, fixed 7-point scale
76
+ - **General (text feedback only):** `type="general"`, no rating scale — see [General](#general-text-feedback-only)
64
77
 
65
78
  ## Installation
66
79
 
@@ -78,24 +91,28 @@ yarn add react-feedback-surveys
78
91
  import 'react-feedback-surveys/index.css';
79
92
  ```
80
93
 
81
- ## Survey Components
94
+ ## Survey Component
95
+
96
+ Every format is reached through the same `Survey` import — `type` and `points` pick the methodology and scale length, `scaleStyle` picks the visual style. See [Format Props](#format-props) for the full matrix of valid `type`/`points`/`scaleStyle` combinations.
82
97
 
83
- ### CSAT5 (Customer Satisfaction Score, 5-Point Scale)
98
+ ### CSAT (Customer Satisfaction Score)
84
99
 
85
- Surveys to ask users about their overall satisfaction.
100
+ Surveys to ask users about their overall satisfaction, or about a specific feature or flow.
86
101
 
87
102
  **Example questions:**
88
103
  - "How satisfied are you with our product?"
89
- - "How would you rate your overall experience?"
90
- - "How satisfied are you with our customer support?"
104
+ - "Was this search helpful?"
105
+ - "Are you satisfied with the checkout process?"
91
106
 
92
- <img alt="CSAT5" src="docs/assets/csat5.png" width="416" />
107
+ <img alt="CSAT, 5-point scale" src="docs/assets/csat5.png" width="416" />
93
108
 
94
109
  ```tsx
95
- import { CSAT5Survey } from 'react-feedback-surveys';
110
+ import { Survey } from 'react-feedback-surveys';
96
111
  import 'react-feedback-surveys/index.css';
97
112
 
98
- <CSAT5Survey
113
+ <Survey
114
+ type="csat"
115
+ points={5}
99
116
  scaleStyle="emoji"
100
117
  question="How would you rate your satisfaction with our product?"
101
118
  minLabel="Very unsatisfied"
@@ -110,24 +127,12 @@ import 'react-feedback-surveys/index.css';
110
127
  />
111
128
  ```
112
129
 
113
- `scaleStyle`: `emoji` | `numbers` | `stars`.
114
-
115
- ### CSAT2 (Customer Satisfaction Score, 2-Point Scale)
116
-
117
- Surveys to ask users about specific features or flows.
118
-
119
- **Example questions:**
120
- - "Was this search helpful?"
121
- - "Did you find what you were looking for?"
122
- - "Are you satisfied with the checkout process?"
123
-
124
- <img alt="CSAT2" src="docs/assets/csat2.png" width="386" />
130
+ <img alt="CSAT, 2-point scale" src="docs/assets/csat2.png" width="386" />
125
131
 
126
132
  ```tsx
127
- import { CSAT2Survey } from 'react-feedback-surveys';
128
- import 'react-feedback-surveys/index.css';
129
-
130
- <CSAT2Survey
133
+ <Survey
134
+ type="csat"
135
+ points={2}
131
136
  scaleStyle="thumbs"
132
137
  question="Are you satisfied with the result?"
133
138
  responseType="text"
@@ -140,26 +145,27 @@ import 'react-feedback-surveys/index.css';
140
145
  />
141
146
  ```
142
147
 
143
- `scaleStyle`: `emoji` | `thumbs`.
148
+ `points={5}` → `scaleStyle`: `emoji` | `numbers` | `stars`. `points={2}` → `scaleStyle`: `emoji` | `thumbs`.
144
149
 
145
- ### NPS10 (Net Promoter Score, 0–10 Scale)
150
+ ### NPS (Net Promoter Score)
146
151
 
147
- Surveys to ask users if they'd recommend your product.
152
+ Surveys to ask users if they'd recommend your product. Fixed 0–10 scale — no `points` prop needed.
148
153
 
149
154
  **Example questions:**
150
155
  - "How likely are you to recommend us to a friend or colleague?"
151
156
  - "On a scale of 0-10, would you recommend our service?"
152
157
  - "How likely are you to recommend this product to others?"
153
158
 
154
- <img alt="NPS10" src="docs/assets/nps10.png" width="616" />
159
+ <img alt="NPS" src="docs/assets/nps10.png" width="616" />
155
160
 
156
- <img alt="NPS10 mobile" src="docs/assets/nps10-mobile.png" width="340" />
161
+ <img alt="NPS mobile" src="docs/assets/nps10-mobile.png" width="340" />
157
162
 
158
163
  ```tsx
159
- import { NPS10Survey } from 'react-feedback-surveys';
164
+ import { Survey } from 'react-feedback-surveys';
160
165
  import 'react-feedback-surveys/index.css';
161
166
 
162
- <NPS10Survey
167
+ <Survey
168
+ type="nps"
163
169
  scaleStyle="numbers"
164
170
  question="How likely are you to recommend our product/service to a friend or colleague?"
165
171
  minLabel="Very unlikely"
@@ -176,22 +182,23 @@ import 'react-feedback-surveys/index.css';
176
182
 
177
183
  `scaleStyle`: `numbers`.
178
184
 
179
- ### CES7 (Customer Effort Score, 7-Point Scale)
185
+ ### CES (Customer Effort Score)
180
186
 
181
- Surveys to ask users how easy it is to use your product.
187
+ Surveys to ask users how easy it is to use your product. Fixed 7-point scale — no `points` prop needed.
182
188
 
183
189
  **Example questions:**
184
190
  - "How easy was it to complete your task?"
185
191
  - "How much effort did it take to resolve your issue?"
186
192
  - "How easy was it to sign up for an account?"
187
193
 
188
- <img alt="CES7" src="docs/assets/ces7.png" width="436" />
194
+ <img alt="CES" src="docs/assets/ces7.png" width="436" />
189
195
 
190
196
  ```tsx
191
- import { CES7Survey } from 'react-feedback-surveys';
197
+ import { Survey } from 'react-feedback-surveys';
192
198
  import 'react-feedback-surveys/index.css';
193
199
 
194
- <CES7Survey
200
+ <Survey
201
+ type="ces"
195
202
  scaleStyle="numbers"
196
203
  question="How easy was it to complete your task?"
197
204
  minLabel="Very difficult"
@@ -208,6 +215,31 @@ import 'react-feedback-surveys/index.css';
208
215
 
209
216
  `scaleStyle`: `numbers`.
210
217
 
218
+ ### General (text feedback only)
219
+
220
+ No rating scale — just text feedback and an optional screenshot. The text feedback step is shown immediately and is the whole survey; there's no `points`/`scaleStyle`/`minLabel`/`maxLabel`/`question` to set.
221
+
222
+ **Example use cases:**
223
+ - A general "Send feedback" or "Report a problem" trigger with no methodology attached.
224
+ - Contexts where a numeric rating doesn't make sense.
225
+
226
+ <img alt="General" src="docs/assets/general.png" width="368" />
227
+
228
+ ```tsx
229
+ import { Survey } from 'react-feedback-surveys';
230
+ import 'react-feedback-surveys/index.css';
231
+
232
+ <Survey
233
+ type="general"
234
+ textQuestion="Got feedback? We'd love to hear it"
235
+ textButtonSendLabel="Send"
236
+ thankYouMessage="Thank you for your feedback!"
237
+ onFeedbackSubmit={({ text }) => {/* ... */}}
238
+ />
239
+ ```
240
+
241
+ `responseType` defaults to `'text'` (also accepts `'choices'`) — it can't be unset, since the text feedback step is the only content the survey has. The step is mandatory: there's no Skip button (`textButtonSkipLabel` is ignored for `type="general"`), and Submit stays disabled until there's something to send.
242
+
211
243
  ## Layout Components
212
244
 
213
245
  ### Popup
@@ -219,7 +251,7 @@ The `<Popup>` component wraps survey widgets in a fixed overlay that slides in f
219
251
  #### Usage
220
252
 
221
253
  ```tsx
222
- import { Popup, CSAT5Survey } from 'react-feedback-surveys';
254
+ import { Popup, Survey } from 'react-feedback-surveys';
223
255
  import 'react-feedback-surveys/index.css';
224
256
 
225
257
  <Popup
@@ -232,7 +264,9 @@ import 'react-feedback-surveys/index.css';
232
264
  placement="bottomRight"
233
265
  onClose={() => console.log('Closed')}
234
266
  >
235
- <CSAT5Survey
267
+ <Survey
268
+ type="csat"
269
+ points={5}
236
270
  scaleStyle="stars"
237
271
  question="How would you rate your satisfaction?"
238
272
  onScoreSubmit={({ value }) => {/* ... */}}
@@ -249,9 +283,13 @@ import 'react-feedback-surveys/index.css';
249
283
  | `className` | `string` | - | - | Additional CSS class name for the popup container. |
250
284
  | `classNames` | `{ base?: string; content?: string; close?: string }` | - | - | Optional class names for internal popup elements. |
251
285
  | `children` | `React.ReactNode` | - | - | Content to render inside the popup (typically a survey component). |
286
+ | `closeLabel` | `string` | - | `'Close survey'` | Close button label, used for both its `aria-label` and `title`. |
252
287
  | `onClose` | `() => void` | - | - | Callback fired when the close button is clicked. |
253
288
 
254
- For more examples, check out the Storybook stories (e.g., `CSAT5Survey.stories.tsx`, `CSAT2Survey.stories.tsx`).
289
+ For more examples, check out the Storybook stories under `widgets/Survey` (grouped by CSAT/NPS/CES in the sidebar).
290
+
291
+ > **Note**
292
+ > If the survey inside uses `dir="rtl"` on a page that's otherwise LTR (or vice versa), pass the same `dir` to `Popup` too — the close button's position resolves against the document direction, while the survey head's reserved offset resolves against the survey's own `dir`. Passing both keeps them aligned.
255
293
 
256
294
  ### Surface
257
295
 
@@ -265,11 +303,13 @@ The Surface component provides:
265
303
  #### Usage
266
304
 
267
305
  ```tsx
268
- import { Surface, CSAT5Survey } from 'react-feedback-surveys';
306
+ import { Surface, Survey } from 'react-feedback-surveys';
269
307
  import 'react-feedback-surveys/index.css';
270
308
 
271
309
  <Surface className="custom-surface">
272
- <CSAT5Survey
310
+ <Survey
311
+ type="csat"
312
+ points={5}
273
313
  scaleStyle="stars"
274
314
  question="How would you rate your satisfaction?"
275
315
  onScoreSubmit={({ value }) => {/* ... */}}
@@ -288,7 +328,7 @@ The Surface component uses the `--ft-surface-padding`, `--ft-surface-padding-mob
288
328
 
289
329
  ## Props
290
330
 
291
- Most props are shared across all survey widgets. Each widget differs only in its `scaleStyle` values.
331
+ Most props are shared across every survey format. `type` (and `points`, for CSAT) select the format; `scaleStyle` picks its visual style — see [Format Props](#format-props).
292
332
 
293
333
  ### Shared Props
294
334
 
@@ -296,14 +336,17 @@ Most props are shared across all survey widgets. Each widget differs only in its
296
336
  |--------------------|-------------------------------|----------|------------------------------------------------------------------------------|
297
337
  | `classNames` | `ClassNamesConfig` (see below)| - | Optional class names to target internal parts. |
298
338
  | `dir` | `'ltr' \| 'rtl' \| 'auto'` | - | Text direction for RTL/LTR language support. |
299
- | `question` | `string` | required | Main survey question displayed on the first screen. |
300
- | `minLabel` | `string` | - | Left label for the scale. |
301
- | `maxLabel` | `string` | - | Right label for the scale. |
302
- | `responseType` | `null \| 'text' \| 'choices'` | - | Enables optional follow-up feedback. |
339
+ | `strings` | `SurveyStrings` (see below) | - | Overrides for the library's own aria-labels and other screen-reader-only text. See [Accessibility Labels](#accessibility-labels). |
340
+ | `question` | `string` | required (not used for `type="general"`) | Main survey question displayed on the first screen. |
341
+ | `minLabel` | `string` | - | Left label for the scale. Not used for `type="general"`. |
342
+ | `maxLabel` | `string` | - | Right label for the scale. Not used for `type="general"`. |
343
+ | `getScoreLabelSuffix` | `(label: string) => string` | - | Builds the visible text appended after the first/last numbered scale button when it carries `minLabel`/`maxLabel`. Default `` (label) => ` - ${label}` ``. Applies to the `numbers` scale style (CSAT5, CES, NPS). |
344
+ | `responseType` | `null \| 'text' \| 'choices'` (`'text' \| 'choices'` for `type="general"`, defaults to `'text'`) | - | Enables optional follow-up feedback. |
303
345
  | `textQuestion` | `string` | - | Follow-up question displayed when `responseType` is defined. |
304
346
  | `textButtonSendLabel` | `string` | - | Submit label for the feedback screen. |
305
347
  | `textButtonSkipLabel` | `string` | - | Skip label for the feedback screen. |
306
348
  | `choiceOptions` | `string[] \| null` | - | Predefined choices (when `responseType === 'choices'`). |
349
+ | `otherPlaceholder` | `string` | - | Placeholder for the free-text input next to choice checkboxes. Default `'Other'`. |
307
350
  | `thankYouMessage` | `string` | required | Message shown after submission. |
308
351
  | `collectContact` | `boolean` | - | Enables an optional email collection step before the success screen. |
309
352
  | `userId` | `string` | - | Existing user identity. When provided, the email collection step is skipped. |
@@ -311,6 +354,43 @@ Most props are shared across all survey widgets. Each widget differs only in its
311
354
  | `contactSubtext` | `string` | - | Descriptive text shown above the email input. |
312
355
  | `contactButtonSendLabel` | `string` | - | Submit label for the email collection screen. |
313
356
  | `contactButtonSkipLabel` | `string` | - | Skip label for the email collection screen. |
357
+ | `onCaptureScreenshot` | `() => string \| Blob \| Promise<string \| Blob>` | - | Enables an optional screenshot-attachment control on the feedback step. Hidden unless provided — see [Attachments](#attachments). |
358
+ | `screenshotButtonLabel` | `string` | - | Label for the screenshot-attachment control. |
359
+ | `screenshotErrorMessage` | `string` | - | Shown to the respondent when `onCaptureScreenshot` fails. Default `'Failed to capture screenshot'`. |
360
+ | `maxAttachments` | `number` | - | Maximum number of attachments a respondent may confirm. Default `1`. See [Attachments](#attachments). |
361
+ | `attachmentCaption` | `string` | - | Visible caption under an attachment thumbnail, also used as its image alt text. Default `'Screenshot'`. |
362
+
363
+ ### Accessibility Labels
364
+
365
+ A handful of internal aria-labels ship with an English default. These are never visible UI copy (that's `question`, `thankYouMessage`, `otherPlaceholder`, `attachmentCaption`, etc., always authored by you, listed alongside the rest of the shared props above) — every key in `strings` is announced to assistive tech only, useful to override if you're localizing a survey for a non-English audience.
366
+
367
+ Pass a `strings` object with only the keys you want to change — each one merges over its own English default, so there's no need to repeat the rest:
368
+
369
+ ```tsx
370
+ <Survey
371
+ /* ... */
372
+ strings={{
373
+ emailLabel: 'Adresse e-mail',
374
+ getScoreLabel: (score) => `Score ${score}`
375
+ }}
376
+ />
377
+ ```
378
+
379
+ #### SurveyStrings Type
380
+
381
+ | Key | Type | Default |
382
+ |-----|------|---------|
383
+ | `feedbackFormLabel` | `string` | `'Feedback form'` |
384
+ | `additionalFeedbackLabel` | `string` | `'Additional feedback'` |
385
+ | `yourFeedbackLabel` | `string` | `'Your feedback'` |
386
+ | `contactFormLabel` | `string` | `'Contact form'` |
387
+ | `emailLabel` | `string` | `'Email address'` |
388
+ | `attachmentOpenLabel` | `string` | `'Open screenshot in a new tab'` |
389
+ | `attachmentRemoveLabel` | `string` | `'Remove screenshot'` |
390
+ | `getScoreLabel` | `(score: number) => string` | `(score) => \`Score ${score}\`` |
391
+ | `getStarsLabel` | `(score: number) => string` | `(score) => \`${score} ${score > 1 ? 'stars' : 'star'}\`` |
392
+
393
+ `getScoreLabel` applies to the `numbers` scale style (CSAT5, CES, NPS); `getStarsLabel` applies to CSAT5's `stars` style. Both are callbacks rather than templates so you can apply correct pluralization for your target language. `Popup`'s close button label lives on `Popup` itself, not in `strings` — see its own `closeLabel` prop in [Popup Props](#props).
314
394
 
315
395
  #### ClassNamesConfig Type
316
396
 
@@ -350,10 +430,13 @@ interface ClassNamesConfig {
350
430
 
351
431
  ```typescript
352
432
  type ScorePayload = { value: number };
353
- type FeedbackPayload = { value: number; text?: string | string[] };
433
+ type FeedbackPayload = { value?: number; text?: string | string[]; attachments?: Attachment[] };
354
434
  type ContactPayload = { value?: number; text?: string | string[]; email: string };
435
+ type Attachment = { kind: 'screenshot'; data: string | Blob; name?: string; mimeType?: string; size?: number };
355
436
  ```
356
437
 
438
+ > See [Attachments](#attachments).
439
+
357
440
  > **Event behavior**
358
441
 
359
442
  #### `onScoreSubmit`
@@ -372,13 +455,14 @@ The actual `value` returned depends on the survey type:
372
455
  Invoked when the user completes the follow-up step and submits their feedback (only applies when `responseType` is `text` or `choices`).
373
456
  This callback provides both the original score and the user's input.
374
457
 
375
- The feedback step is always optional: respondents can submit feedback or skip it via the `textButtonSkipLabel` button (or by submitting with empty input), and either action advances to the next screen. `onFeedbackSubmit` only fires when feedback text or choices are actually submitted; it is **not** called when the step is skipped.
458
+ For surveys with a rating step, the feedback step is optional: respondents can submit feedback or skip it via the `textButtonSkipLabel` button (or by submitting with empty input), and either action advances to the next screen. For `type="general"`, the feedback step **is** the survey, so there's no Skip button — Submit stays disabled until there's something to send. `onFeedbackSubmit` only fires when feedback text or choices are actually submitted; it is **not** called when a rating-survey's step is skipped.
376
459
 
377
460
  **Arguments:**
378
- - `value: number` — the same score previously passed to `onScoreSubmit`
461
+ - `value?: number` — the same score previously passed to `onScoreSubmit`; `undefined` for `type="general"`, which has no rating step
379
462
  - `text: string | string[]` — depends on `responseType`:
380
- - `text`: a single text comment
381
- - `choices`: an array of selected options (may include a free-text comment if enabled)
463
+ - `text`: a single text feedback string
464
+ - `choices`: an array of selected options (may include free-text feedback if enabled)
465
+ - `attachments?: Attachment[]` — present when the respondent confirmed a screenshot; see [Attachments](#attachments)
382
466
 
383
467
  > **Important**
384
468
  You should listen to **both** `onScoreSubmit` and `onFeedbackSubmit`.
@@ -399,7 +483,9 @@ When `collectContact` is `true` and no `userId` is provided, an optional email c
399
483
  - `email: string` — the email address entered by the respondent.
400
484
 
401
485
  ```tsx
402
- <CSAT5Survey
486
+ <Survey
487
+ type="csat"
488
+ points={5}
403
489
  scaleStyle="numbers"
404
490
  question="How would you rate your satisfaction with our product?"
405
491
  responseType="text"
@@ -416,35 +502,73 @@ When `collectContact` is `true` and no `userId` is provided, an optional email c
416
502
  />
417
503
  ```
418
504
 
419
- ### Scale Style Options
505
+ ### Attachments
420
506
 
421
- Each survey type supports specific scale styles for displaying the rating interface:
507
+ Respondents can attach a screenshot to their feedback, surfaced on submit as `attachments?: Attachment[]` (see the event payload types above). Each attachment renders as a thumbnail in the feedback step, with a caption and size underneath, click to open it in a new tab, click the "x" to remove it. Submit is disabled for the brief moment a capture is still in flight, so it can't be confirmed before the attachment actually lands in the list.
422
508
 
423
- #### CSAT2Survey
509
+ Once an attachment is confirmed, the Skip button is disabled — an attached screenshot is never silently discarded. Remove the attachment to re-enable Skip, or fill in feedback and submit to keep it. An attachment on its own is never sufficient to submit, either: for `responseType="text"`, submitting with an attachment but no text flags the textarea instead of sending; for `responseType="choices"`, Submit stays disabled until a choice is picked or text is entered.
424
510
 
425
- | Prop | Type | Required | Description |
426
- |--------------------------|-------------------------|----------|-----------------------------------------------------------------------------------|
427
- | `scaleStyle` | `'emoji'` \| `'thumbs'` | required | Emoji mood scale style (happy/sad faces) or thumbs up/down emoji scale style. |
511
+ By default a respondent can confirm a single attachment: once one is attached, the add control hides. Pass `maxAttachments` to raise (or lower) that cap. The add control stays visible (below the existing thumbnails) until the cap is reached:
428
512
 
429
- #### CSAT5Survey
513
+ ```tsx
514
+ <Survey
515
+ /* ... */
516
+ onCaptureScreenshot={() => domToDataUrl(document.body)}
517
+ maxAttachments={3}
518
+ />
519
+ ```
430
520
 
431
- | Prop | Type | Required | Description |
432
- |--------------------------|---------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------|
433
- | `scaleStyle` | `'emoji'` \| `'numbers'` \| `'stars'` | required | Emoji scale style (5 emotion levels), numeric scale style (1–5), or star rating scale style (1–5 stars). |
521
+ Pass `onCaptureScreenshot` to add an "Capture screenshot" control to the feedback step. Clicking it calls your function and attaches the result immediately. The control is off by default: it doesn't render at all unless `onCaptureScreenshot` is provided.
434
522
 
435
- #### CES7Survey
523
+ Capturing a screenshot needs an actual DOM-to-image library (or a native bridge in a hybrid app) — real, non-trivial code that most consumers of this package won't want to pay for in bundle size if they don't use the feature. So react-feedback-surveys deliberately doesn't ship a capture implementation itself: bring your own function that returns the captured image (as a data URL string, a `Blob`, or a `Promise` of either). A drop-in recipe using [modern-screenshot](https://github.com/qq15725/modern-screenshot):
436
524
 
437
- | Prop | Type | Required | Description |
438
- |--------------------------|-------------|----------|------------------------------------|
439
- | `scaleStyle` | `'numbers'` | required | Numeric scale style (1–7). |
525
+ ```shell
526
+ npm i modern-screenshot
527
+ ```
440
528
 
441
- #### NPS10Survey
529
+ ```tsx
530
+ import { Survey } from 'react-feedback-surveys';
531
+ import { domToDataUrl } from 'modern-screenshot';
532
+ import 'react-feedback-surveys/index.css';
442
533
 
443
- | Prop | Type | Required | Description |
444
- |--------------------------|-------------|----------|-------------------------------------|
445
- | `scaleStyle` | `'numbers'` | required | Numeric scale style (0–10). |
534
+ <Survey
535
+ type="csat"
536
+ points={5}
537
+ scaleStyle="numbers"
538
+ question="How would you rate your satisfaction with our product?"
539
+ responseType="text"
540
+ textQuestion="We'd love to hear your thoughts — what can we improve?"
541
+ thankYouMessage="Thanks for your feedback!"
542
+ onCaptureScreenshot={() => domToDataUrl(document.body)}
543
+ onScoreSubmit={({ value }) => {/* ... */}}
544
+ onFeedbackSubmit={({ value, text, attachments }) => {/* attachments?.[0]?.data holds the attached screenshot */}}
545
+ />
546
+ ```
547
+
548
+ In a hybrid app, `onCaptureScreenshot` can just as well call into a native bridge (e.g. `WKWebView.takeSnapshot` on iOS) instead of a DOM-to-image library.
446
549
 
447
- Note: The numeric ranges are defined by the widget (e.g., CSAT5 uses a 1–5 scale, NPS10 uses 0–10).
550
+ > **Note**
551
+ > DOM-to-image capture is best-effort — fonts, cross-origin images, and some CSS effects may not render identically on every browser (particularly Safari/iOS). The respondent can open the attached thumbnail in a new tab to check it, and remove it with one click if the capture came out wrong.
552
+
553
+ ### Format Props
554
+
555
+ | Prop | Type | Required | Description |
556
+ |------|------|----------|--------------|
557
+ | `type` | `'csat'` \| `'nps'` \| `'ces'` \| `'general'` | required | Survey methodology. Determines the scale range and which `scaleStyle`/`points` combinations are valid. `'general'` has no scale at all — see [General](#general-text-feedback-only). |
558
+ | `points` | `2` \| `5` | required for `type="csat"`; not used otherwise | Number of points on the scale. `nps` is a fixed 0–10 scale and `ces` a fixed 1–7 scale, so neither takes `points`. |
559
+
560
+ ### Scale Style Options
561
+
562
+ Valid `scaleStyle` values depend on `type` (and `points`, for CSAT):
563
+
564
+ | `type` | `points` | Scale range | Valid `scaleStyle` |
565
+ |--------|----------|-------------|---------------------|
566
+ | `csat` | `5` | 1–5 | `'emoji'` (5 emotion levels) \| `'numbers'` \| `'stars'` (1–5 stars) |
567
+ | `csat` | `2` | 0–1 | `'emoji'` (happy/sad faces) \| `'thumbs'` (thumbs up/down) |
568
+ | `ces` | — | 1–7 | `'numbers'` |
569
+ | `nps` | — | 0–10 | `'numbers'` |
570
+
571
+ Invalid combinations (e.g. `type="ces"` with `scaleStyle="stars"`) are rejected at the type level.
448
572
 
449
573
  ## Styling
450
574
 
@@ -472,6 +596,9 @@ You can override colors and fonts via CSS variables:
472
596
  /* Error color for validation messages */
473
597
  --ft-color-error: 32 95% 44%;
474
598
 
599
+ /* Error text color (attachment capture errors) — darker than --ft-color-error, tuned for text contrast rather than borders/outlines */
600
+ --ft-color-error-text: 32 95% 32%;
601
+
475
602
  /* Border color for inputs and containers */
476
603
  --ft-color-border: 214 14% 83%;
477
604
 
@@ -493,7 +620,7 @@ You can override colors and fonts via CSS variables:
493
620
  --ft-popup-head-offset: 0;
494
621
 
495
622
  /* Padding for Surface component container (desktop) */
496
- --ft-surface-padding: 24px;
623
+ --ft-surface-padding: 20px;
497
624
 
498
625
  /* Padding for Surface component container on mobile devices (max-width: 400px) */
499
626
  --ft-surface-padding-mobile: 20px;
@@ -523,6 +650,7 @@ Here's an example of dark theme colors that work well with the survey components
523
650
  --ft-color-bg: 220 13% 13%;
524
651
  --ft-color-muted: 214 10% 60%;
525
652
  --ft-color-error: 14 90% 62%;
653
+ --ft-color-error-text: 14 85% 72%;
526
654
  --ft-color-border: 217 10% 28%;
527
655
  --ft-color-outline: 216 12% 45%;
528
656
  --ft-color-shadow: 0 0% 0%;
@@ -549,7 +677,7 @@ Here's an example of dark theme colors that work well with the survey components
549
677
  **Implementation example with React:**
550
678
 
551
679
  ```tsx
552
- import { CSAT5Survey } from 'react-feedback-surveys';
680
+ import { Survey } from 'react-feedback-surveys';
553
681
  import 'react-feedback-surveys/index.css';
554
682
  import { useEffect } from 'react';
555
683
 
@@ -573,7 +701,9 @@ function App() {
573
701
  }, []);
574
702
 
575
703
  return (
576
- <CSAT5Survey
704
+ <Survey
705
+ type="csat"
706
+ points={5}
577
707
  scaleStyle="emoji"
578
708
  question="How satisfied are you with our product?"
579
709
  onScoreSubmit={({ value }) => console.log('Score:', value)}
@@ -608,7 +738,7 @@ For deeper customization strategies, see the section below.
608
738
 
609
739
  ### Custom Classes
610
740
 
611
- All widgets accept a `classNames` prop with two optional groups: `base` (outer shell) and `scale` (the interactive
741
+ `Survey` accepts a `classNames` prop with two optional groups: `base` (outer shell) and `scale` (the interactive
612
742
  rating UI). Pass your own class names to override styles without relying on internal selectors.
613
743
 
614
744
  When is this useful?
@@ -637,13 +767,13 @@ Reference: available keys
637
767
  | `scale.score` | Number inside a scale button (for numeric variants) |
638
768
  | `scale.labels` | Left/Right labels displayed under the scale |
639
769
 
640
- Example: customizing a CSAT5Survey widget
770
+ Example: customizing a `Survey` widget
641
771
 
642
772
  ```tsx
643
- import { CSAT5Survey } from 'react-feedback-surveys';
773
+ import { Survey } from 'react-feedback-surveys';
644
774
  import 'react-feedback-surveys/index.css';
645
775
 
646
- <CSAT5Survey
776
+ <Survey
647
777
  classNames={{
648
778
  base: {
649
779
  base: 'my-survey-base',
@@ -659,6 +789,8 @@ import 'react-feedback-surveys/index.css';
659
789
  labels: 'my-scale-labels',
660
790
  }
661
791
  }}
792
+ type="csat"
793
+ points={5}
662
794
  scaleStyle="numbers"
663
795
  question="How would you rate your satisfaction with our product?"
664
796
  minLabel="Very unsatisfied"