
Skill
launchdarkly-flag-qualitative-feedback-setup
integrate LaunchDarkly qualitative user feedback
Description
Integrate LaunchDarkly qualitative user feedback into a JavaScript/TypeScript codebase. Guides framework and design system detection, builds the sendFeedback utility and feedback widget matching existing project patterns. Use when the user wants to add a Give Feedback widget, collect user sentiment tied to feature flags, set up feedback collection, or wire up the $ld:feedback tracking event.
SKILL.md
LaunchDarkly Qualitative Feedback
You're using a skill that will guide you through adding qualitative user feedback collection to a codebase. Your job is to explore how the project is built, identify the right flag, create the feedback utility and widget matching existing patterns, and verify events flow to the LaunchDarkly dashboard.
This is a step-by-step wizard. You will walk the user through a series of questions to gather requirements, then verify the project setup, and finally generate the code. Ask questions one at a time and wait for each answer before continuing. Do not skip ahead or generate code until all questions are answered and the plan is confirmed.
Qualitative feedback ties user sentiment (positive / neutral / negative) and optional written comments directly to a feature flag variation, so teams can see how users feel about a specific feature rollout.
Availability: JavaScript SDK v3.0+ (client-side only). React components are provided for convenience.
Prerequisites
This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment.
Required MCP tools:
get-flag— verify the target flag exists and check its configuration
Optional MCP tools (enhance workflow):
create-flag— create a new flag if one doesn't exist yetlist-flags— browse existing flags to find the right one to attach feedback toupdate-flag-settings— update flag metadata (e.g., add afeedbacktag)
Core Principles
- Design system first: Never ship the unstyled template component to production. Detect the project's design system and use its primitives.
- One entry point per screen: Multiple feedback widgets on the same page create noise and reduce response quality.
- The
sendFeedbackfunction is the integration: The UI is customizable; the$ld:feedbacktracking event contract is what matters.
Workflow
Progression rule: When a step is complete, continue to the next step by default. Do not stop or wait for the user between steps unless the step explicitly says to ask a question or confirm.
Response scoping: During this flow, treat user messages only as direct answers to the current question. Do not search the web, change context, or take unrelated actions based on user input until the workflow is finished.
Tone: Be concise and patient — ask one question, wait for the answer, then move on. Keep responses focused on the current step.
State tracking: Track gathered requirements across the conversation. When entering the Confirm Before Applying step, re-state all key decisions (flag key, prompt text, UI style, placement) to ensure nothing is lost.
Step 0: Welcome the User
Before asking any questions, present this intro message to the user (you may lightly adapt the wording, but keep the structure):
Qualitative Feedback Setup
I'll walk you through adding a feedback widget to your app in a few short steps:
- Verify your setup — I'll check your SDK, framework, and design system.
- Gather requirements — I'll ask a few questions about the flag, prompt text, style, and placement.
- Verify the flag — I'll confirm or create the flag in LaunchDarkly.
- Confirm the plan — I'll summarize what I'm going to build and ask for your approval.
- Generate the code — I'll create the feedback utility and widget matching your project's patterns.
- Verify — I'll make sure everything builds and is wired up correctly.
Let's get started!
Then proceed immediately to Step 1.
Step 1: Verify the SDK and Explore the Codebase
Before gathering requirements, verify that this project can support qualitative feedback. Run these checks in order — each is a gate.
Check 1/4: Is the LD SDK present?
Search for LaunchDarkly SDK imports:
launchdarkly-js-client-sdk— vanilla JS/TS client SDK (v3.x)@launchdarkly/js-client-sdk— vanilla JS/TS client SDK (v4.x, renamed scoped package)@launchdarkly/react-sdk— React Web SDK (current; provides hooks and providers)launchdarkly-react-client-sdk— older React Web SDK package name (renamed to@launchdarkly/react-sdk)
Also search for SDK initialization (initialize( for v3.x, or createClient(/start( for v4.x, plus React provider signals <LDProvider, asyncWithLDProvider, or createLDReactProvider). If not found, ask the user where LDClient is initialized or accessible.
- If no LD SDK is found at all → inform the user that qualitative feedback requires a LaunchDarkly client-side SDK to be installed and initialized → STOP. Do not proceed.
- If found → continue to Check 2.
Check 2/4: Is it a client-side SDK?
Qualitative feedback is a client-side only feature. Verify the SDK found in Check 1 is a client-side SDK. Refer to references/client-side-sdk-list.md and references/server-side-sdk-list.md for the complete lists.
If the SDK is server-side only with no client SDK present → explain that qualitative feedback requires a client-side SDK and cannot be sent from server code → STOP. Do not proceed.
If unclear, ask the user whether their SDK is client-side or server-side before continuing.
Check 3/4: Is the SDK version compatible?
Check package.json for the exact version. The $ld:feedback event requires v3.0+ of the JavaScript/React SDK.
- If version < 3.0 → inform the user they need to upgrade before feedback can be added → STOP. Do not proceed.
- If version >= 3.0 → continue to Check 4.
Check 4/4: Is feedback already configured?
Search for $ld:feedback, sendFeedback, FeedbackPopover, or Give feedback to check for existing feedback integration.
- If a
sendFeedbackfunction exists that callsclient.track('$ld:feedback', ...)→ reuse it. Record its import path — Step 5 will be skipped. Continue to Step 2. - If a feedback widget exists but not the
sendFeedbackutility → note the widget pattern for consistency. - If nothing found → continue normally.
Remaining exploration
After the checks above pass, gather the remaining context:
- Find the SDK initialization. Search for:
initialize(fromlaunchdarkly-js-client-sdk(v3.x)createClient(/start(from@launchdarkly/js-client-sdk(v4.x)<LDProvider,asyncWithLDProvider, orcreateLDReactProviderfrom the React SDK- How the
LDClientinstance is accessed (direct reference, React context, custom hook, etc.)
- Check for observability. Search for
@launchdarkly/observabilityand@launchdarkly/session-replay. If present, feedback can be linked to session replays. - Detect the UI framework and design system.
Signal Framework react,react-domin depsReact vuein depsVue (adapt vanilla JS pattern to Vue component) @angular/corein depsAngular (adapt vanilla JS pattern to Angular component) sveltein depsSvelte (adapt vanilla JS pattern to Svelte component) None of the above Vanilla JS/TS
Then check for a design system or component library:@mui/material,@emotion/react→ MUI@chakra-ui/react→ Chakra UIantd→ Ant Design@radix-ui/*or@shadcn/*→ Radix/shadcntailwindcssin deps ortailwind.config.*→ Tailwind CSSbootstraporreact-bootstrap→ Bootstrap- Custom design system → look for a
components/directory with shared primitives (Button, Modal, Popover, etc.)
If a design system is detected, use its primitives (Button, Popover, TextArea, IconButton) instead of the inline-styled template component. The template components in references/ are a starting point — always adapt them to match the project's existing patterns.
Step 2: Understand the Goal
STOP. Do not proceed to Step 3 until you have answers to ALL of the following. Check the user's request — if it already answers a question, you don't need to re-ask it. For anything not covered, ask the user and wait for their response before continuing.
Ask only one question at a time. Wait for the user's answer before asking the next. Do not list multiple questions in one message. When presenting options, use AskUserQuestion to render interactive selectors in the agent console. For free-text questions (like prompt text), ask as a normal text message.
- Which feature/flag? Feedback is always tied to a flag key. Search the codebase for existing flag keys, then use
AskUserQuestionwith the discovered flags as options (plus a "Create a new flag" option). If no flags are found, ask as a text question. - What question do you want to ask the user? This becomes the prompt text shown in the feedback widget (e.g., "How do you feel about this view?" or "Did this work as expected?"). Ask this as a normal text message since it requires free-text input.
- What type of feedback? Use
AskUserQuestionwith header "Type" and these options:- label: "Sentiment + text (Recommended)", description: "Sentiment buttons with a text area for comments, in a popover"
- label: "Sentiment only", description: "One-click sentiment buttons inline — no popover, no text input"
- label: "Text only", description: "Just a text area for written feedback in a popover, no sentiment buttons"
If the user selects "Sentiment + text" or "Sentiment only", follow up with
AskUserQuestionheader "Icons" and these options: - label: "Thumbs up / down", description: "Two buttons: positive / negative"
- label: "Smiley faces", description: "Three buttons: positive / neutral / negative"
- Where should the feedback widget go? Before asking, search the codebase for pages, routes, or main components. Then use
AskUserQuestionwith header "Placement" and 2–4 concrete placement options that make sense for this repo (e.g., "Next to the results heading on the Simulator page", "In the page header", "Below the main content area"). Prefer page-level placement — don't bury feedback in small sub-components unless it's workflow-specific confirmation.
Step 3: Verify the Flag in LaunchDarkly
Use get-flag to confirm the target flag exists and is configured for client-side use.
- If the flag doesn't exist, use
create-flag(or direct the user to the flag create skill). - If the flag exists but doesn't have client-side SDK availability enabled, inform the user they need to enable it in the flag's Advanced Controls section.
- If the flag was created or verified, provide the user with a direct link to the flag in the LaunchDarkly dashboard.
- Optionally use
update-flag-settingsto add afeedbacktag.
Step 4: Confirm Before Applying
STOP. Do not write any code until the user explicitly confirms the plan. This applies every time — including when the user provides all details upfront, when re-running for a second flag in the same conversation, or when reusing existing utilities. A detailed user request is not implicit approval.
Summarize the planned changes using future tense (e.g., "Files to create", "Files to modify") — no code has been written yet:
- Flag key and whether it exists or needs creation
- The
sendFeedbackutility: where it will live, whether it will be reused from a prior step, and whether it includes session replay - The feedback widget: component name, UI style, placement location
- Which files will be created or modified
Then use AskUserQuestion with header "Confirm" and these options:
- label: "Looks good", description: "Proceed with the plan as described"
- label: "Change something", description: "I want to adjust part of the plan before you start"
If the user selects "Change something" (or provides custom input), address their feedback and re-present the updated plan with the same confirmation prompt. Do not proceed to Step 5 until the user selects "Looks good".
Step 5: Add the sendFeedback Function
If Check 4 found an existing sendFeedback function, skip this step entirely. Use the recorded import path from that check and proceed directly to Step 6.
This is the core integration. Create a utility function that sends the $ld:feedback tracking event.
Start from the template in references/sendFeedback.ts. For JavaScript projects, adapt by removing type annotations.
The essential contract:
client.track('$ld:feedback', {
feedback_answer: string, // required — the user's written feedback
flag_key: string, // required — the flag this feedback is about
sentiment: "positive" | "neutral" | "negative", // defaults to "neutral"
feedback_prompt: string, // optional — the question shown to the user
o11y_session_id: string, // optional — links to session replay
custom_properties: Record<string, any>, // optional — extra metadata to attach to the feedback event
});
client.flush();
Key decisions:
- Match the
LDClienttype import to the project's SDK version:launchdarkly-js-client-sdkfor v3.x,@launchdarkly/js-client-sdkfor v4.x. The templates import fromlaunchdarkly-js-client-sdk— update it if the project is on v4. (Theclient.track/client.flushcalls are the same across both.) - If
@launchdarkly/session-replayis in the project, include the session ID viaLDRecord.getSession()?.sessionSecureID. If not, remove the session replay import ando11y_session_idlogic from the template. - Place the utility where the project keeps its LD-related code (alongside existing flag helpers, in a
lib/orutils/directory, etc.) - Export the
LDFeedbackSentimenttype if using TypeScript
Verification: After creating the file, search the codebase to confirm it exists and contains the expected content — a sendFeedback export that calls client.track('$ld:feedback', ...) followed by client.flush(). If not found, fix before proceeding.
Step 6: Build the Feedback Widget
Build the UI using the project's existing design system and component patterns.
Start from the appropriate template in references/. For JavaScript projects, adapt by removing type annotations.
- Sentiment + text → PopoverFeedback.tsx with
icons="thumbs"oricons="smileys" - Text only → PopoverFeedback.tsx with
icons="none" - Sentiment only → InlineFeedback.tsx with
icons="thumbs"oricons="smileys"
These templates use inline styles and SVG icons so they work without any CSS framework or icon library. When the project has a design system, replace template elements with its primitives:
| Template element | Replace with |
|---|---|
Inline style={{...}} | Project's CSS approach (Tailwind classes, CSS modules, styled-components, etc.) |
<button> | Design system's <Button> component |
Positioned <div> popover | Design system's <Popover> or <Dropdown> component |
<textarea> | Design system's <TextArea> or <Input> component |
| Inline SVG icons | Project's icon library (Lucide, Heroicons, MUI icons, etc.) |
Always use fill="currentColor" (not hardcoded hex colors) in SVG icons so they inherit the parent's text color.
Do NOT use find-and-replace for PROMPT_TO_REPLACE. Instead, make prompt a string prop on the component. This makes the widget reusable across flags without code duplication. Pass the user's prompt text from Step 2 as a prop when rendering.
Standard UX pattern (follow this regardless of design system):
- A trigger button with a speech-bubble icon and "Give feedback" label (for popover styles)
- A popover that opens on click, containing:
- A text area with the prompt text as its placeholder
- Sentiment controls matching the style chosen in Step 2:
- Thumbs up / thumbs down — two icon buttons toggling positive/negative
- Smiley-face scale — three icon buttons for positive (smile), neutral (meh), negative (frown)
- Text-only — no sentiment buttons; omit the
sentimentfield fromsendFeedback
- A submit button
- After submission: Track a
submittedstate. Replace the widget with a "Thanks for your feedback!" confirmation message. Do not just close the popover silently — the user needs to know their feedback was received. - Quick thumbs / inline smileys (no popover): inline prompt text with sentiment buttons; sends feedback immediately on click with no text input. After click, replace with a thank-you message.
Placement: Use the location chosen in Step 2. Do not add multiple feedback entry points on the same screen.
Wire the component into the target
After building the widget, add it to the target location from Step 2:
- Find the target file. Search the codebase for the page or component matching the placement chosen in Step 2. State the file path you found and ask the user to confirm it's correct before modifying it. If they say no, ask for clarification and try again.
- Import and render. Import the feedback component into the confirmed target file. Render it and pass the required props:
flagKeystring andpromptstring. For React SDK projects, the component should use theuseLDClient()hook internally rather than acceptingldClientas a prop. - Verification: Search the target component to confirm it imports and renders the feedback component with the required props (
flagKey,prompt). If the import or render is missing, fix before proceeding.
Framework-specific approach
React — Start from the template, then adapt to the project's design system using the table above.
Vanilla JS/TS — Wire up DOM elements to the sendFeedback function:
submitButton.addEventListener('click', () => {
sendFeedback(client, flagKey, feedbackInput.value, selectedSentiment, promptText);
});
Vue / Angular / Svelte — Translate the React component pattern into the framework's idiom. The sendFeedback function is framework-agnostic; only the UI wrapper changes.
Step 7: Verify
Pre-condition check: Before verifying, confirm that all prior steps produced their expected outputs:
- (a) The
sendFeedbackutility file exists (or an existing one was reused from Check 4). - (b) The feedback widget component file exists.
- (c) The widget is imported and rendered in the target location with the required props.
If any are missing, go back and complete the relevant step before proceeding.
Walk the user through validation step by step:
- Run the build. Execute the project's build or lint command. If it fails, fix the errors before continuing.
- Start the app locally. Ask the user to run their dev server (or run it yourself if you can). Confirm it starts without errors.
- Navigate to the feedback location. Tell the user to open the page where the widget was placed. Ask them to confirm the "Give feedback" button (or inline thumbs) is visible and styled correctly.
- Submit test feedback. Walk the user through: click the trigger → enter test text → select a sentiment → click Send. Ask them to open the browser's Network tab and confirm a POST to the LaunchDarkly events endpoint fired containing
$ld:feedback. - Check the dashboard. Provide a direct link to the flag's Feedback tab in the LaunchDarkly dashboard. Tell the user to wait ~1 minute and then refresh — their test feedback should appear.
- Session replay (if applicable). If
@launchdarkly/session-replayis configured, ask the user to check for a Replay button next to the feedback entry in the dashboard.
After the user confirms everything works, mention:
- They can subscribe Slack channels to receive feedback notifications: go to the flag's Feedback tab → Subscribe → select Slack channels (requires the LaunchDarkly Slack app).
- Provide the direct link to the flag's Feedback tab so they can bookmark it for monitoring.
Edge Cases
| Situation | Action |
|---|---|
| No LD SDK found | STOP — handled by Check 1 |
| Server-side SDK only, no client SDK | STOP — handled by Check 2 |
| SDK version < 3.0 | STOP — handled by Check 3 |
Existing sendFeedback function found | Reuse it, skip Step 5 — handled by Check 4 |
React SDK (@launchdarkly/react-sdk / launchdarkly-react-client-sdk) | Access client via useLDClient() hook instead of a direct reference |
| Flag doesn't have client-side availability | User must enable it in the flag's Advanced Controls |
| Observability not installed | Skip the o11y_session_id field; session replay won't be available |
| No design system detected | Use the template component with minimal inline styles as a starting point |
| Multiple flags need feedback | Create separate sendFeedback calls per flag; each widget targets one flag key |
What NOT to Do
- Don't ship the unstyled template component to production — always adapt to the project's design system.
- Don't add multiple feedback widgets on the same screen.
- Don't forget to call
client.flush()afterclient.track(). - Don't hardcode flag keys — use the project's existing constant/config pattern for flag keys.
- Don't send feedback from server-side code — this is a client-side feature only.
Templates
Code templates live in references/ alongside the SDK lists:
| File | Description |
|---|---|
| sendFeedback.ts | Utility that sends the $ld:feedback tracking event, with session replay support |
| PopoverFeedback.tsx | Popover with text area + configurable sentiment icons (icons="thumbs" / "smileys" / "none") |
| InlineFeedback.tsx | Inline one-click sentiment buttons (icons="thumbs" / "smileys") |
All templates are TypeScript — for JavaScript projects, adapt by removing type annotations. Templates include inline SVG icons and work without external icon libraries. Adapt them to the project's design system and pass the user's prompt text as a prop.
References
Example Flows
These show how the decision tree plays out for common scenarios.
Happy path — React + TypeScript, new flag, thumbs popover:
Step 0 → welcome. Step 1 → finds @launchdarkly/react-sdk, React + Tailwind, no existing feedback. Step 2 → user picks a new flag checkout-redesign, prompt "How do you feel about the new checkout?", thumbs style, placed below the order summary. Step 3 → creates flag via create-flag. Step 4 → user approves. Step 5 → creates sendFeedback.ts in lib/ld/. Step 6 → creates FeedbackPopover.tsx adapted to Tailwind, wires it into CheckoutPage.tsx with useLDClient(). Step 7 → build passes, event fires.
Existing sendFeedback — reuse and skip:
Step 1 → Check 4 finds src/utils/sendFeedback.ts already calling client.track('$ld:feedback', ...). Records the import path. Step 2 → gathers requirements normally. Steps 3–4 → as usual. Step 5 → skipped (reuses existing function). Step 6 → builds widget, imports sendFeedback from the existing path. Step 7 → build passes, event fires.
Server-side SDK — early STOP:
Step 1 → Check 1 finds @launchdarkly/node-server-sdk. Check 2 → it's in server-side-sdk-list.md, no client SDK present. → Informs user that qualitative feedback requires a client-side SDK. STOP.
No design system — minimal styles:
Step 1 → finds launchdarkly-js-client-sdk v3.2, no React, no Tailwind, no component library. Step 6 → uses the template with inline styles as the baseline, adapts to the project's vanilla JS patterns and existing CSS conventions.
More skills from the ai-tooling repository
View all 50 skillsagent-graphs
create and manage agent graphs
Jul 28AgentsArchitectureLaunchDarklyMulti-Agentaiconfig-agent-graphs
manage agent graphs
May 22AgentsArchitectureaiconfig-ai-metrics
manage built-in AI metrics
May 22AnalyticsLaunchDarklyMetricsaiconfig-create
redirect to configs-create skill
May 22LaunchDarklyReferenceaiconfig-custom-metrics
configure custom metrics in LaunchDarkly
May 22AnalyticsFeature FlagsLaunchDarklyaiconfig-migrate
redirect to migrate skill
May 22Reference
More from LaunchDarkly
View publisheraiconfig-online-evals
run online evaluations
ai-tooling
May 22EvalsLaunchDarklyTestingaiconfig-projects
manage AI configuration projects
ai-tooling
May 22ConfigurationLaunchDarklyaiconfig-snippets
manage AI configuration snippets
ai-tooling
May 22ConfigurationLaunchDarklyaiconfig-targeting
configure LaunchDarkly targeting rules
ai-tooling
May 22ConfigurationFeature FlagsLaunchDarklyaiconfig-tools
redirect to tools skill
ai-tooling
May 22LaunchDarklyReferenceaiconfig-update
redirect to configs-update skill
ai-tooling
May 22LaunchDarklyReference