@sensefolks/pricepoint
v1.0.0
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. WCAG 2.1 AA accessible.
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
- ♿ WCAG 2.1 AA accessible (keyboard, screen reader, reduced motion)
- 🎨 Customizable via CSS Parts and Custom Properties
- 📱 Responsive design
- 🌐 Works with any framework (React, Vue, Angular, 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/pricepoint/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>
<!-- Legacy browsers -->
<script nomodule src="https://unpkg.com/@sensefolks/pricepoint/dist/sf-pricepoint/sf-pricepoint.js"></script>Or via jsDelivr:
<script type="module" src="https://cdn.jsdelivr.net/npm/@sensefolks/pricepoint/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>API Reference
Properties
| Property | Attribute | Type | Default | Description |
| ----------- | ------------ | -------- | ----------- | -------------------------------------------------- |
| surveyKey | survey-key | string | undefined | Public key (UUID) of the survey to load (required) |
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 Parts
Style the component externally using ::part():
/* Container & Layout */
sf-pricepoint::part(survey-container) {
/* Main wrapper */
}
sf-pricepoint::part(heading) {
/* Step headings */
}
sf-pricepoint::part(question-text) {
/* Question text */
}
sf-pricepoint::part(progress-indicator) {
/* Step/phase progress */
}
/* Van Westendorp Phase */
sf-pricepoint::part(vw-container) {
/* VW phase wrapper */
}
sf-pricepoint::part(slider-track) {
/* Slider track */
}
sf-pricepoint::part(slider-thumb) {
/* Slider thumb */
}
sf-pricepoint::part(text-input) {
/* Price text input fields */
}
sf-pricepoint::part(validation-message) {
/* Validation error text */
}
/* Gabor-Granger Phase */
sf-pricepoint::part(gg-container) {
/* GG phase wrapper */
}
sf-pricepoint::part(price-display) {
/* Price label in GG */
}
sf-pricepoint::part(radio-group) {
/* Purchase intent radio group */
}
sf-pricepoint::part(radio-option) {
/* Individual radio option */
}
sf-pricepoint::part(radio-option-selected) {
/* Selected radio option */
}
/* Navigation & Actions */
sf-pricepoint::part(nav-button) {
/* Navigation buttons */
}
sf-pricepoint::part(submit-button) {
/* Submit button */
}
/* States */
sf-pricepoint::part(loading-message) {
/* Loading state text */
}
sf-pricepoint::part(error-message) {
/* Error message text */
}
sf-pricepoint::part(error-container) {
/* Error wrapper with retry */
}
sf-pricepoint::part(retry-button) {
/* Retry button */
}
sf-pricepoint::part(completion-screen) {
/* Completion state */
}
/* Accessibility */
sf-pricepoint::part(announcements) {
/* Screen reader live region */
}CSS Custom Properties
sf-pricepoint {
--sf-primary-color: #4f46e5; /* Primary accent color */
--sf-background-color: #ffffff; /* Background color */
--sf-text-color: #1f2937; /* Text color */
--sf-border-radius: 8px; /* Border radius for inputs and cards */
--sf-font-family: system-ui, sans-serif; /* Font family */
--sf-spacing-scale: 1; /* Spacing multiplier (1 = default) */
}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+
- IE11 (with ES5 build)
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" }],
"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" }
],
"phases": ["loading", "vw", "gg", "complete", "error"],
"cssCustomProperties": ["--sf-primary-color", "--sf-background-color", "--sf-text-color", "--sf-border-radius", "--sf-font-family", "--sf-spacing-scale"]
}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
