Skip to main content
userTourKit
@tour-kit/surveysHeadless

HeadlessQuestionSelect

Headless select — manages single or multi-select state and returns groupProps and getOptionProps with correct ARIA roles for each mode

domidex01Published

HeadlessQuestionSelect manages value state and returns ARIA props that adapt to mode. In single mode, options receive role="radio"; in multi mode they receive role="checkbox".

Import

import { HeadlessQuestionSelect } from '@tour-kit/surveys/headless';

Usage

Single-select

import { HeadlessQuestionSelect } from '@tour-kit/surveys/headless';

const options = [
  { value: 'price', label: 'Price' },
  { value: 'features', label: 'Missing features' },
  { value: 'support', label: 'Poor support' },
];

<HeadlessQuestionSelect
  id="cancel-reason"
  label="Why are you leaving?"
  options={options}
  mode="single"
  onChange={(value) => survey.answer('cancel-reason', value)}
>
  {({ value, groupProps, getOptionProps }) => (
    <div {...groupProps} className="flex flex-col gap-2">
      {options.map((opt) => {
        const optProps = getOptionProps(opt.value);
        return (
          <div
            key={opt.value}
            {...optProps}
            onClick={() => setValue(opt.value)}
            className={`p-3 border rounded cursor-pointer ${optProps['aria-checked'] ? 'border-blue-600 bg-blue-50' : ''}`}
          >
            {opt.label}
          </div>
        );
      })}
    </div>
  )}
</HeadlessQuestionSelect>

Multi-select

<HeadlessQuestionSelect
  id="reasons"
  label="Select all that apply"
  options={options}
  mode="multi"
  onChange={(value) => survey.answer('reasons', value)}
>
  {({ options: opts, groupProps, getOptionProps, setValue, value }) => (
    <div {...groupProps} className="flex flex-col gap-2">
      {opts.map((opt) => {
        const optProps = getOptionProps(opt.value);
        const current = Array.isArray(value) ? value : [];
        return (
          <div
            key={opt.value}
            {...optProps}
            onClick={() => {
              const next = current.includes(opt.value)
                ? current.filter((v) => v !== opt.value)
                : [...current, opt.value];
              setValue(next);
            }}
            className={`p-3 border rounded cursor-pointer ${optProps['aria-checked'] ? 'border-blue-600' : ''}`}
          >
            {opt.label}
          </div>
        );
      })}
    </div>
  )}
</HeadlessQuestionSelect>

Props

Prop

Type

Render prop shape

Prop

Type

Free & open source

Ship onboarding, not config.

npm i @tour-kit/core is MIT and free. The Pro packages work unlicensed too — a one-time $99 license removes the production watermark when you ship.

MIT-licensed — no signup, no credit card. Pay once, only when you ship.