UserChoice

UserChoice presents product concepts and records the choices people make between them. Use those repeated choices to study which attributes and combinations influence preference.

@sensefolks/userchoiceWCAG 2.1 AA

What UserChoice Measures

UserChoice presents a series of product configurations ("concepts") and asks respondents to choose among them. Across tasks, this reveals which attributes and combinations drive preference.

For a deeper methodology overview, see theconjoint analysis guide.

Choices and task progress stay only in the mounted component. Reloading the page starts again at the first task with blank answers.

When UserChoice Is the Right Survey

Use it when people must choose between realistic combinations and you need to understand the trade-offs behind those choices.

  • Product concepts — Compare realistic combinations of features, price, or service levels
  • Plan design — Learn which bundles people prefer when benefits compete
  • Concept validation — Compare candidate configurations before building
  • Beta and early-access research — Study preference with an engaged audience

Installation

CDN (Recommended)

html
<script type="module" src="https://unpkg.com/@sensefolks/[email protected]/dist/sf-userchoice/sf-userchoice.esm.js"></script>

NPM

bash
npm install @sensefolks/userchoice

Embed UserChoice

HTML

html
<sf-userchoice 
  survey-key="your-survey-uuid" 
  completion-message="Thank you for your feedback!">
</sf-userchoice>

React

jsx
import '@sensefolks/userchoice';

function App() {
  return (
    <sf-userchoice 
      survey-key="your-survey-uuid" 
      completion-message="Thank you!">
    </sf-userchoice>
  );
}

Vue

vue
<template>
  <sf-userchoice 
    survey-key="your-survey-uuid" 
    completion-message="Thank you!">
  </sf-userchoice>
</template>

<script>
import '@sensefolks/userchoice';
export default {name: 'App'};
</script>

Angular

typescript
// app.module.ts
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import '@sensefolks/userchoice';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})

For React + Next.js, Vue + Nuxt, Svelte, and Astro examples, see theEmbedding Tutorial.

API Reference

Properties

PropertyAttributeTypeDefaultDescription
surveyKeysurvey-keystringRequired. UUID of the survey from your dashboard
completionMessagecompletion-messagestring'Thank you for your response!'Message shown after submission
thankYouMessagethank-you-messagestringOverrides the saved survey thank-you message
sessionDataRecord<string, string | number | boolean>{}Complete typed Session Data matching every field in the schema defined during survey creation

Events

EventDescriptionDetail Properties
sfErrorFired on load, submit, or validation errorssurveyKey, errorType, errorMessage

CSS Custom Properties

Override these custom properties to theme the component:

PropertyDefaultDescription
--sf-primary#005fccPrimary accent color
--sf-primary-hover#0047a3Primary color hover state
--sf-text-primary#111827Primary text color
--sf-text-secondary#6b7280Secondary/muted text color
--sf-error-color#dc2626Error state color
--sf-error-text#991b1bError message text color
--sf-card-bg#ffffffCard/container background
--sf-card-border#d1d5dbCard/container border color
--sf-card-radius8pxCard/container border radius
--sf-button-radius6pxButton border radius
--sf-transition150ms easeDefault transition timing
--sf-selected-bg#e8f0feSelected card background
--sf-selected-border#005fccSelected card border color
--sf-card-shadow0 1px 3px rgba(0,0,0,0.1)Card shadow
--sf-card-shadow-hover0 4px 12px rgba(0,0,0,0.15)Card hover shadow
--sf-none-bg#f9fafb"None" option background
--sf-none-border#9ca3af"None" option border
--sf-error-bg#fef2f2Error state background
--sf-error-border#fecacaError state border
--sf-progress-bg#e5e7ebProgress bar track background
--sf-progress-fill#005fccProgress bar fill color
css
/* Override theme tokens on the component */
sf-userchoice {
  --sf-primary: #7c3aed;
  --sf-primary-hover: #6d28d9;
  --sf-card-radius: 12px;
  --sf-button-radius: 8px;
  --sf-selected-bg: #ede9fe;
  --sf-selected-border: #7c3aed;
  --sf-selected-ring: rgba(0, 95, 204, 0.2);
  --sf-button-text: #ffffff;
  --sf-none-hover-bg: #f3f4f6;
}

CSS Parts

Style individual elements inside the shadow DOM using ::part(). UserChoice has a large number of parts because of its multi-step, card-based UI:

css
/* Survey container */
sf-userchoice::part(survey-container) { }

/* Steps */
sf-userchoice::part(step) { }
sf-userchoice::part(choice-task-step) { }
sf-userchoice::part(survey-fields-step) { }
sf-userchoice::part(completion-step) { }

/* Headings */
sf-userchoice::part(heading) { }
sf-userchoice::part(task-heading) { }
sf-userchoice::part(survey-fields-heading) { }
sf-userchoice::part(completion-heading) { }

/* Task header & progress */
sf-userchoice::part(task-header) { }
sf-userchoice::part(task-instructions) { }
sf-userchoice::part(progress-indicator) { }
sf-userchoice::part(progress-label) { }
sf-userchoice::part(progress-track) { }
sf-userchoice::part(progress-bar) { }
sf-userchoice::part(progress-fill) { }
sf-userchoice::part(progress-text) { }

/* Concepts */
sf-userchoice::part(concepts-container) { }
sf-userchoice::part(concept) { }
sf-userchoice::part(concept-selected) { }
sf-userchoice::part(concept-unselected) { }
sf-userchoice::part(none-option) { }
sf-userchoice::part(concept-header) { }
sf-userchoice::part(concept-radio) { }
sf-userchoice::part(concept-title) { }
sf-userchoice::part(concept-attributes) { }
sf-userchoice::part(concept-attribute) { }
sf-userchoice::part(attribute-label) { }
sf-userchoice::part(attribute-value) { }

/* Buttons */
sf-userchoice::part(button-container) { }
sf-userchoice::part(button) { }
sf-userchoice::part(back-button) { }
sf-userchoice::part(next-button) { }
sf-userchoice::part(submit-button) { }

/* Survey Fields form */
sf-userchoice::part(form-container) { }
sf-userchoice::part(form-field) { }
sf-userchoice::part(form-label) { }
sf-userchoice::part(form-input) { }
sf-userchoice::part(form-select) { }
sf-userchoice::part(hcaptcha-container) { }
sf-userchoice::part(input) { }
sf-userchoice::part(select) { }
sf-userchoice::part(required-indicator) { }
sf-userchoice::part(radio-group) { }
sf-userchoice::part(radio-option) { }
sf-userchoice::part(radio-input) { }
sf-userchoice::part(radio-label) { }
sf-userchoice::part(checkbox-group) { }
sf-userchoice::part(checkbox-option) { }
sf-userchoice::part(checkbox-input) { }
sf-userchoice::part(checkbox-label) { }

/* Completion */
sf-userchoice::part(completion-summary) { }
sf-userchoice::part(summary-text) { }

/* Messages & errors */
sf-userchoice::part(message) { }
sf-userchoice::part(error-message) { }
sf-userchoice::part(loading-message) { }
sf-userchoice::part(error-container) { }
sf-userchoice::part(troubleshooting-link) { }

/* Branding */
sf-userchoice::part(branding) { }
sf-userchoice::part(branding-link) { }
sf-userchoice::part(branding-logo) { }

/* Accessibility */
sf-userchoice::part(announcements) { }

Accessibility

  • Full keyboard navigation (Tab, Arrow keys, Enter/Space)
  • ARIA labels and live regions for screen readers
  • Progress announcements ("Choice Task 2 of 5")
  • Focus indicators and high contrast mode support
  • Respects prefers-reduced-motion

Browser Support

BrowserVersion
Chrome88+
Firefox85+
Safari14+
Edge88+
IE11Not supported