A lightweight in-app survey widget supporting radio, checkbox, and freeform text answer types. Displays 1-3 questions in either a modal or inline variant. Designed for quick contextual feedback collection without leaving the current page.
Introduction
Overview
Product teams need to collect targeted user feedback at specific moments (post-action, post-session, feature evaluation). A full survey tool is overkill; this widget provides a 1-3 question micro-survey that can be embedded inline or shown as a modal overlay.
Goals
Support radio (single-select), checkbox (multi-select), and text (freeform) answer types.
Allow 1-3 questions per survey instance.
Provide modal and inline display variants.
Submit all answers via a single callback.
Ship Storybook stories covering all question types and variants.
Adds significant complexity; out of scope for micro-surveys
Survey builder
Admin tooling is a separate product concern
Response analytics
Consumer responsibility
More than 3 questions
Micro-survey pattern; longer surveys use dedicated tools
Users and Pain Points
User
Pain Point
Product managers
No quick way to collect contextual feedback without third-party tools
Developers
Integrating third-party survey SDKs for simple 1-2 question surveys
End users
Disruptive full-page surveys when only a quick question is needed
Definitions
Term
Definition
Micro-survey
A short survey (1-3 questions) designed for in-context feedback
Radio question
Single-select from a list of options
Checkbox question
Multi-select from a list of options
Text question
Freeform text input for open-ended responses
Current State
No in-app survey widget exists. The NPS widget (FRD-064) handles the specific NPS use case. For general micro-surveys, products either use third-party tools (Pendo, Hotjar) or build custom forms. No reusable pattern exists in the design system.
Proposed Solution
Create an InAppSurvey widget at src/components/widgets/in-app-survey.tsx that:
Accepts an array of SurveyQuestion objects (1-3 items).
Renders each question with the appropriate input type (radio group, checkbox group, or textarea).
Provides modal and inline variants via a variant prop.
Collects answers in local state and submits all at once via onSubmit.
Shows a thank-you/confirmation state after submission.
Supports a dismiss action for the modal variant.
Requirements
The widget must validate that at least required questions are answered before enabling submit. It must be fully controlled for visibility (modal variant) and delegate all response handling to the consumer.
Functional Requirements
ID
Requirement
Priority
FR-01
Render 1-3 questions based on the questions array
Must
FR-02
Support radio answer type with configurable options
Must
FR-03
Support checkbox answer type with configurable options
Must
FR-04
Support text answer type with configurable placeholder
Must
FR-05
Mark questions as required or optional
Must
FR-06
Disable submit until all required questions are answered
Must
FR-07
Call onSubmit(answers) with a map of question ID to answer value(s)
Must
FR-08
Show a thank-you state after submission
Must
FR-09
Support variant: "modal" rendering inside a Dialog
Must
FR-10
Support variant: "inline" rendering within page flow
Must
FR-11
Modal variant supports dismiss via onDismiss callback
Must
FR-12
Support a survey title and optional description
Should
Non-Functional Requirements
ID
Requirement
NFR-01
Bundle size under 3 KB gzipped
NFR-02
Full light/dark theme support
NFR-03
Renders within one frame; no lazy loading needed for 1-3 questions
API / Interface Requirements
interface SurveyOption {
value:string;
label:string;
}
interface SurveyQuestion {
id:string;
type:"radio"|"checkbox"|"text";
prompt:string;
options?:SurveyOption[]; // required for radio and checkbox
placeholder?:string; // for text type
required?:boolean; // default true
}
type SurveyAnswers =Record<string, string|string[]>;
interface InAppSurveyProps {
questions:SurveyQuestion[]; // 1-3 items
title?:string;
description?:string;
variant?:"modal"|"inline"; // default "inline"
open?:boolean; // for modal variant
onOpenChange?:(open:boolean)=>void;
submitLabel?:string; // default "Submit"
thankYouMessage?:string; // default "Thanks for your feedback!"