@sensefolks/pricepoint
v3.0.3
Published
Find the right price that your users are willing to pay
Maintainers
Readme
sf-pricepoint
Price sensitivity survey web component for Van Westendorp and Gabor-Granger pricing research.
Features
- 💰 Van Westendorp Price Sensitivity Meter + Gabor-Granger demand analysis
- 🔄 Automatic two-phase pipeline (VW → GG) based on response volume
- 💱 Multi-currency support with auto-detection from respondent locale
- ♿ Accessibility-oriented keyboard, screen reader, and reduced motion support
- 🎨 Customizable via CSS Parts and Custom Properties
- 📱 Responsive design
- 🌐 Works with React + Next.js, Vue + Nuxt, Angular, Svelte, Astro, and vanilla HTML
Installation
NPM
npm install @sensefolks/pricepointYarn
yarn add @sensefolks/pricepointCDN (Script Tag)
<!-- Modern browsers (ES modules) -->
<script type="module" src="https://unpkg.com/@sensefolks/[email protected]/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>Or via jsDelivr:
<script type="module" src="https://cdn.jsdelivr.net/npm/@sensefolks/[email protected]/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>Quick Start
<sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>React
import '@sensefolks/pricepoint';
function App() {
return <sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>;
}Vue
<template>
<sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>
</template>
<script>
import '@sensefolks/pricepoint';
export default { name: 'App' };
</script>Angular
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@sensefolks/pricepoint';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}<!-- component.html -->
<sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>Framework Notes
- React + Next.js: use the React snippet above. In Next.js, render surveys inside a
'use client'component. - Vue + Nuxt: use the Vue snippet above. In Nuxt pages that SSR, wrap the survey with
<ClientOnly>. - Svelte: load the package inside
onMountand render the samesf-*tag. - Astro: keep the
sf-*tag in markup and load the package with a client-side module import. - Vanilla JS: use the HTML/CDN snippet or a module import in a
<script type="module">block.
For complete copy-paste examples, see: https://sensefolks.com/docs/tutorials/embedding-a-survey/
API Reference
Properties
| Property | Attribute | Type | Default | Description |
| ------------------- | -------------------- | -------- | -------------------------------- | -------------------------------------------------- |
| surveyKey | survey-key | string | undefined | Public key (UUID) of the survey to load (required) |
| completionMessage | completion-message | string | 'Thank you for your response!' | Fallback message shown after submission when the survey has no saved thank-you message |
| thankYouMessage | thank-you-message | string | undefined | Overrides the saved survey thank-you message |
| sessionData | — | object | {} | Session data passed from the host app; must match the declared Session Data schema |
Events
| Event | Detail Type | Description |
| --------------- | -------------------------- | ------------------------------------------------- |
| sfReady | SfReadyEventDetail | Survey config loaded and component ready |
| sfSubmit | SfSubmitEventDetail | Response successfully submitted |
| sfError | SfErrorEventDetail | Error occurred (load, submit, validation, config) |
| sfPhaseChange | SfPhaseChangeEventDetail | Phase transition (loading → vw → gg → complete) |
Event Payload Shapes
interface SfReadyEventDetail {
surveyKey: string;
productType: string;
phase: string;
}
interface SfSubmitEventDetail {
surveyKey: string;
phase: string;
completionTimeSeconds: number;
}
interface SfErrorEventDetail {
surveyKey: string;
errorType: 'load' | 'submit' | 'validation' | 'config';
errorMessage: string;
}
interface SfPhaseChangeEventDetail {
phase: string; // 'loading' | 'vw' | 'gg' | 'complete' | 'error'
}Configuration Schema
The survey is configured server-side. The component fetches config via the surveyKey.
{
"productContext": {
"productName": "My SaaS Product",
"productCategory": "Software",
"industry": "Technology"
},
"targetMarkets": ["US", "GB"],
"productType": "subscription",
"billingInterval": "monthly",
"priceRange": { "min": 10, "max": 200 },
"currencyMode": "USD",
"costAnalysis": {
"annualCostPerCustomer": 50,
"annualFixedExpenses": 100000,
"expectedAnnualCustomers": 5000,
"desiredProfitMargin": 30
}
}CSS Shadow Parts
The component uses shadow DOM and exposes the following CSS parts for styling:
| Part | Description |
| ----------------------- | ----------------------------------------------------------------- |
| survey-container | Outer wrapper for the entire survey |
| heading | Primary heading elements |
| progress | Top progress header wrapper |
| progress-indicator | Compatibility alias for the progress header |
| progress-label | "Task X of Y" label |
| progress-text | Compatibility alias for the progress label |
| progress-track | Continuous progress bar track |
| progress-bar | Compatibility alias for the progress bar track |
| progress-fill | Filled portion of the progress bar |
| vw-container | Van Westendorp phase wrapper |
| question-container | Wrapper for each question |
| question-text | Question text paragraph |
| validation-errors | Validation error container |
| validation-error | Individual validation error message |
| button-container | Wrapper for action buttons |
| submit-button | The submit/next action button |
| error-container | Wrapper for error state display |
| error-message | Error message text |
| troubleshooting-link | Troubleshooting link shown with integration errors |
| retry-button | Retry button shown on error |
| loading-message | Loading state text |
| completion-step | Completion/thank-you screen wrapper |
| hcaptcha-container | Wrapper for the hCaptcha widget (when enabled) |
| branding | SenseFolks branding wrapper |
| branding-link | SenseFolks branding link |
| branding-logo | SenseFolks branding logo |
| gg-container | Gabor-Granger phase wrapper |
| price-display | Price value display span |
| radio-group | Wrapper for radio button group |
| radio-option | A single radio option row |
| radio-option-selected | Applied to the selected radio option (additive with radio-option) |
| radio-input | Survey Field radio input |
| radio-label | Radio option label text |
| survey-fields-container | Survey Fields section wrapper |
| form-container | Form container compatibility alias |
| survey-fields-heading | Survey Fields section heading |
| form-field | Individual Survey Field wrapper |
| form-label | Survey Field label |
| required-indicator | Required field marker |
| survey-field-control | Survey Field input wrapper |
| form-input | Text/email/number Survey Field input |
| form-select | Dropdown Survey Field input |
| field-error | Survey Field validation error |
| checkbox-group | Checkbox Survey Field group |
| checkbox-option | Checkbox Survey Field option wrapper |
| checkbox-input | Checkbox Survey Field input |
| checkbox-label | Checkbox Survey Field label |
| announcements | ARIA live region for screen reader announcements |
CSS Custom Properties
| Property | Default | Description |
| --------------------- | ----------------------- | -------------------------------- |
| --sf-primary | #005fcc | Primary accent color |
| --sf-primary-hover | #0047a3 | Primary color hover state |
| --sf-text-primary | #111827 | Primary text color |
| --sf-text-secondary | #6b7280 | Secondary/muted text color |
| --sf-error-color | #dc2626 | Error state color |
| --sf-error-text | #991b1b | Integration error text color |
| --sf-error-bg | #fef2f2 | Integration error background |
| --sf-error-border | #fecaca | Integration error border color |
| --sf-card-bg | #ffffff | Card/container background |
| --sf-card-border | #d1d5db | Card/container border color |
| --sf-button-text | #ffffff | Button text color |
| --sf-card-radius | 8px | Card/container border radius |
| --sf-button-radius | 6px | Button border radius |
| --sf-transition | 150ms ease | Default transition timing |
| --sf-font-family | system-ui, sans-serif | Font family (backward-compat) |
| --sf-spacing-scale | 1 | Spacing multiplier — 1 = default |
| --sf-progress-bg | #e5e7eb | Progress track color |
| --sf-progress-fill | primary | Progress fill color |
Legacy aliases
--sf-primary-color,--sf-text-color,--sf-background-color, and--sf-border-radiusare still honoured via CSS fallback chains for backward compatibility.
Styling example
/* Override primary color and card radius */
sf-pricepoint {
--sf-primary: #7c3aed;
--sf-primary-hover: #6d28d9;
--sf-card-radius: 12px;
--sf-button-radius: 8px;
}
/* Style the submit button */
sf-pricepoint::part(submit-button) {
font-weight: 600;
letter-spacing: 0.025em;
text-transform: uppercase;
}
/* Highlight the selected radio option */
sf-pricepoint::part(radio-option-selected) {
box-shadow: 0 0 0 2px var(--sf-primary);
}
/* Style the price display in the Gabor-Granger phase */
sf-pricepoint::part(price-display) {
font-size: 1.25rem;
font-weight: 700;
}Privacy And Network Requirements
sf-pricepoint loads survey configuration from the SenseFolks embed request endpoint and submits responses to the SenseFolks embed response endpoint. The survey-key value is validated as a UUID before requests are made and is URL encoded in request paths.
Submitted payloads can include the respondent's answers, configured survey fields, prepared session data values supplied by the host page, completion timing, and metadata including user-agent string, platform, language, cookie-enabled status, online status, screen resolution, color depth, timezone, and timestamp. This component does not use cookies, localStorage, sessionStorage, or IndexedDB. In-progress answers live only in the current component instance; reloading the page or creating a new instance always starts a blank response.
If hCaptcha is enabled by the survey config, allow hCaptcha in your CSP: script-src https://js.hcaptcha.com, frame-src https://hcaptcha.com https://*.hcaptcha.com, and a connect-src that includes your SenseFolks API origin plus the hCaptcha endpoints.
Accessibility
- Full keyboard navigation (Tab, Enter, Space, Arrow keys)
- ARIA
role="slider"witharia-valuemin,aria-valuemax,aria-valuenow,aria-labelon hybrid slider inputs - ARIA
role="radiogroup"on Gabor-Granger purchase intent scale aria-live="polite"region for screen reader announcements on phase/question transitions- Visible focus indicators on all interactive elements
- Respects
prefers-reduced-motion: reduce - High contrast mode support via
@media (prefers-contrast: high)
Browser Support
- Chrome 88+
- Firefox 85+
- Safari 14+
- Edge 88+
Internet Explorer 11 is not supported. The host browser must provide modern
Web Component APIs, fetch, and AbortController.
Agent Integration
This section is designed for AI code generation agents integrating the sf-pricepoint component.
Machine-Readable Config Example
{
"component": "sf-pricepoint",
"package": "@sensefolks/pricepoint",
"attributes": [
{
"name": "survey-key",
"type": "string",
"required": true,
"description": "Public key UUID of the survey"
},
{
"name": "completion-message",
"type": "string",
"required": false,
"description": "Fallback message shown after submission when the survey has no saved thank-you message. Defaults to \"Thank you for your response!\""
}
],
"events": [
{ "name": "sfReady", "detail": "SfReadyEventDetail", "description": "Component loaded and ready" },
{ "name": "sfSubmit", "detail": "SfSubmitEventDetail", "description": "Response submitted" },
{ "name": "sfError", "detail": "SfErrorEventDetail", "description": "Error occurred" },
{ "name": "sfPhaseChange", "detail": "SfPhaseChangeEventDetail", "description": "Phase changed" }
],
"cssCustomProperties": [
"--sf-primary",
"--sf-primary-hover",
"--sf-text-primary",
"--sf-text-secondary",
"--sf-error-color",
"--sf-error-text",
"--sf-error-bg",
"--sf-error-border",
"--sf-card-bg",
"--sf-card-border",
"--sf-button-text",
"--sf-card-radius",
"--sf-button-radius",
"--sf-transition",
"--sf-font-family",
"--sf-spacing-scale",
"--sf-progress-bg",
"--sf-progress-fill"
],
"cssParts": [
"survey-container",
"heading",
"progress",
"progress-indicator",
"progress-label",
"progress-text",
"progress-track",
"progress-bar",
"progress-fill",
"vw-container",
"question-container",
"question-text",
"validation-errors",
"validation-error",
"button-container",
"submit-button",
"error-container",
"error-message",
"troubleshooting-link",
"retry-button",
"loading-message",
"completion-step",
"hcaptcha-container",
"branding",
"branding-link",
"branding-logo",
"gg-container",
"price-display",
"radio-group",
"radio-option",
"radio-option-selected",
"radio-label",
"announcements"
]
}Emitted Events with Payload Shapes
| Event | Payload Fields | When Emitted |
| --------------- | ----------------------------------------------------------------- | --------------------------- |
| sfReady | surveyKey: string, productType: string, phase: string | After config fetch and init |
| sfSubmit | surveyKey: string, phase: string, completionTimeSeconds: number | After successful POST |
| sfError | surveyKey: string, errorType: string, errorMessage: string | On any error |
| sfPhaseChange | phase: string | On phase transition |
Integration Checklist
- Install the package:
npm install @sensefolks/pricepoint - Import the component:
import '@sensefolks/pricepoint'; - Add the element:
<sf-pricepoint survey-key="YOUR_KEY"></sf-pricepoint> - Listen for events:
el.addEventListener('sfReady', (e) => { ... }) - Apply custom styles via CSS custom properties or
::part()selectors - Ensure the survey is created and configured via the API before embedding
TypeScript Types
All types are exported from the package entry point:
import type {
PricepointSurveyConfig,
PriceLadderResponse,
CurrencyResolution,
VWAnswers,
SfReadyEventDetail,
SfSubmitEventDetail,
SfErrorEventDetail,
SfPhaseChangeEventDetail,
} from '@sensefolks/pricepoint';Changelog
See CHANGELOG.md.
License
MIT © SenseFolks
