npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-minimal-survey-builder

v0.4.1

Published

The Headless Survey Engine for Modern React Apps — lightweight, customizable, JSON-schema driven survey builder

Readme

react-minimal-survey-builder

The Headless Survey Engine for Modern React Apps.

A lightweight, customizable, JSON-schema driven survey builder for React. Zero heavy dependencies. Headless-first. TypeScript-first.

Bundle Size TypeScript License: MIT


Why another survey library?

| Feature | SurveyJS | react-minimal-survey-builder | | ------------ | ---------------- | ---------------------------- | | Bundle Size | Heavy (~200kb+) | ~13kb gzipped | | Headless | Limited | Fully headless | | Custom UI | Hard to override | Easy, plug-and-play | | TypeScript | Partial | First-class | | Dependencies | Many | Zero heavy deps | | WCAG A11y | Partial | Full WCAG 2.1 AA | | SSR | Tricky | Full support |


Installation

npm install react-minimal-survey-builder

Peer dependencies: react >= 17, react-dom >= 17


Quick Start

1. Define a schema

import type { SurveySchema } from "react-minimal-survey-builder";

const schema: SurveySchema = {
  id: "survey-1",
  title: "Customer Feedback",
  pages: [
    {
      id: "page-1",
      title: "About You",
      questions: [
        {
          id: "name",
          type: "text",
          label: "What is your name?",
          required: true,
        },
        {
          id: "role",
          type: "select",
          label: "Your role?",
          options: [
            { label: "Developer", value: "dev" },
            { label: "Designer", value: "design" },
            { label: "PM", value: "pm" },
          ],
        },
      ],
    },
  ],
};

2. Render with zero config

import { SurveyRenderer } from "react-minimal-survey-builder";

function App() {
  return (
    <SurveyRenderer
      schema={schema}
      options={{
        onSubmit: (answers) => console.log(answers),
      }}
    />
  );
}

That's it! You get validation, navigation, progress tracking, and conditional logic out of the box.


Architecture

The library has 3 layers — use only what you need:

┌───────────────────────────────────────┐
│      Builder (Drag & Drop UI)         │  ← Optional
├───────────────────────────────────────┤
│    React Layer (Hook + Components)    │  ← Optional UI
├───────────────────────────────────────┤
│    Core Engine (Framework Agnostic)   │  ← Always available
└───────────────────────────────────────┘

Import paths

// Everything
import {
  useSurvey,
  SurveyRenderer,
  SurveyBuilder,
} from "react-minimal-survey-builder";

// Core only (framework agnostic, ~3kb)
import {
  createSurveyManager,
  validateSurvey,
} from "react-minimal-survey-builder/core";

// Builder only
import { SurveyBuilder } from "react-minimal-survey-builder/builder";

API Reference

useSurvey(schema, options?)

The main React hook. Fully headless — bring your own UI.

const {
  survey, // Parsed schema
  answers, // Current answers: Record<string, AnswerValue>
  setAnswer, // (questionId, value) => void
  errors, // ValidationError[]
  getError, // (questionId) => string | undefined
  isValid, // boolean
  validate, // () => ValidationError[]
  getVisibleQuestions, // () => Question[]
  getPageQuestions, // (pageIndex) => Question[]
  visiblePages, // Page[]
  currentPageIndex,
  totalPages,
  hasNextPage,
  hasPrevPage,
  nextPage, // () => boolean
  prevPage, // () => boolean
  goToPage, // (index) => boolean
  submit, // () => Promise<{ success, errors }>
  isSubmitted, // boolean
  reset, // () => void
  progress, // 0-100
} = useSurvey(schema, {
  initialAnswers: { name: "John" },
  onChange: (answers, questionId) => {},
  onSubmit: async (answers) => {},
  onValidate: (errors) => {},
  onPageChange: (pageIndex) => {},
});

<SurveyRenderer />

Pre-built survey UI with full override capabilities.

<SurveyRenderer
  schema={schema}
  options={{ onSubmit: handleSubmit }}
  components={{
    text: MyCustomTextInput,
    radio: MyCustomRadioGroup,
  }}
  renderHeader={({ title, progress }) => (
    <MyHeader title={title} progress={progress} />
  )}
  renderFooter={({ nextPage, submit, isLastPage }) => <MyFooter />}
  renderComplete={() => <ThankYou />}
/>

Render prop mode for complete control:

<SurveyRenderer schema={schema}>
  {(survey) => (
    <div>
      {survey.getPageQuestions(survey.currentPageIndex).map((q) => (
        <MyQuestion key={q.id} question={q} value={survey.answers[q.id]} />
      ))}
    </div>
  )}
</SurveyRenderer>

<SurveyBuilder />

Drag-and-drop visual builder with live preview.

import { SurveyBuilder } from "react-minimal-survey-builder";

function BuilderPage() {
  const [schema, setSchema] = useState(initialSchema);

  return (
    <SurveyBuilder
      value={schema}
      onChange={setSchema}
      showPreview // Side-by-side live preview
      showJson // JSON editor tab
      layout="horizontal" // or "vertical"
    />
  );
}

Schema Reference

SurveySchema

type SurveySchema = {
  id: string;
  title?: string;
  description?: string;
  pages: Page[];
  settings?: {
    showProgress?: boolean; // default: true
    allowBack?: boolean; // default: true
    validateOnPageChange?: boolean; // default: true
    showQuestionNumbers?: boolean; // default: true
    submitText?: string; // default: "Submit"
    nextText?: string; // default: "Next"
    prevText?: string; // default: "Previous"
  };
};

Question

type Question = {
  id: string;
  type:
    | "text"
    | "textarea"
    | "radio"
    | "checkbox"
    | "select"
    | "number"
    | "email"
    | "phone"
    | "url"
    | "password"
    | "date"
    | "time"
    | "datetime"
    | "slider"
    | "rating"
    | "boolean"
    | "file"
    | (string & {}); // custom types
  label: string;
  description?: string;
  placeholder?: string;
  required?: boolean;
  defaultValue?: AnswerValue;
  options?: { label: string; value: string }[];
  visibleIf?: string; // Conditional logic expression
  validation?: ValidationRule[];
  meta?: Record<string, unknown>; // Custom metadata
  // File upload specific
  accept?: string; // e.g. ".pdf,image/*"
  maxFiles?: number; // default 1
  maxFileSize?: number; // bytes per file
  maxTotalSize?: number; // bytes total
  // Other type-specific props: sliderMin/Max/Step, rateCount, countryCode,
  // minTime/maxTime, minDatetime, controlType, hideLabel, passwordShowToggle…
};

Conditional Logic

Use visibleIf to show/hide questions dynamically:

// Simple comparison
{
  visibleIf: "{role} === 'other'";
}

// Multiple conditions
{
  visibleIf: "{age} >= 18 && {country} === 'US'";
}

// OR logic
{
  visibleIf: "{plan} === 'pro' || {plan} === 'enterprise'";
}

// Truthy check
{
  visibleIf: "{newsletter}";
}

Validation Rules

{
  validation: [
    { type: "required" },
    { type: "email", message: "Please enter a valid email" },
    { type: "minLength", value: 3 },
    { type: "maxLength", value: 100 },
    { type: "min", value: 0 },
    { type: "max", value: 10 },
    { type: "pattern", value: "^[A-Z]", message: "Must start with uppercase" },
    {
      type: "custom",
      validator: (value, _question, allAnswers) => {
        if (value === "admin") return "Reserved username";
        return true;
      },
    },
  ];
}

File Upload Questions

The file type provides a drag-and-drop upload zone. Use accept, maxFiles, maxFileSize, and maxTotalSize to configure it:

{
  id: "resume",
  type: "file",
  label: "Upload your résumé",
  description: "PDF only, max 5 MB",
  required: true,
  accept: ".pdf",
  maxFiles: 1,
  maxFileSize: 5 * 1024 * 1024,
}

Answers are returned as FileAnswer[]. Access the raw File object for uploads:

import type { FileAnswer } from "react-minimal-survey-builder";

onSubmit: async (answers) => {
  const files = answers["resume"] as FileAnswer[];
  const body = new FormData();
  files.forEach((f) => body.append("resume", f.file, f.name));
  await fetch("/api/upload", { method: "POST", body });
};

Note: The file property on each FileAnswer is a browser-session File object — upload it in onSubmit before the user navigates away. The other properties (name, size, type, lastModified) are plain serialisable values.


Custom Question Types

Override built-in components or register entirely new types:

import type { QuestionComponentProps } from "react-minimal-survey-builder";

// Custom star rating component
const StarRating = ({ question, value, onChange }: QuestionComponentProps) => (
  <div className="star-rating">
    {[1, 2, 3, 4, 5].map((n) => (
      <span
        key={n}
        onClick={() => onChange(n)}
        className={value >= n ? "star active" : "star"}
      >
        ★
      </span>
    ))}
  </div>
);

// Use it
<SurveyRenderer schema={schema} components={{ rating: StarRating }} />;

Core Engine (Framework Agnostic)

Use the core engine without React:

import { createSurveyManager } from "react-minimal-survey-builder/core";

const manager = createSurveyManager(schema, {
  onSubmit: (answers) => saveToServer(answers),
});

manager.setAnswer("name", "John");
manager.nextPage();

const errors = manager.validate();
const state = manager.getState();

// Event system
const unsub = manager.on("answerChanged", (event) => {
  console.log(event.payload);
});

Running the Example

# From the project root
cd example
npm install
npm run dev

The example app demonstrates all three modes:

  • Survey Renderer — pre-built UI with conditional logic
  • Headless Hook — custom UI powered by useSurvey()
  • Drag & Drop Builder — visual schema editor with live preview

Project Structure

src/
├── core/                    # Framework-agnostic engine
│   ├── schema-parser.ts     # Schema parsing & validation
│   ├── validation-engine.ts # Question validation
│   ├── condition-evaluator.ts # Conditional logic
│   └── state-manager.ts     # State management class
├── react/                   # React layer
│   ├── useSurvey.ts         # Main React hook
│   ├── SurveyRenderer.tsx   # Pre-built survey UI
│   └── QuestionRenderer.tsx # Question component system
├── builder/                 # Visual builder
│   ├── SurveyBuilder.tsx    # Drag & drop builder
│   ├── QuestionEditor.tsx   # Question property editor
│   └── builder-reducer.ts   # Builder state management
├── types/                   # TypeScript types
│   └── index.ts
└── index.ts                 # Main entry point

Design Principles

  • No UI lock-in — Use headless hooks or swap every component
  • Zero heavy dependencies — Only React as a peer dep
  • Tree-shakable — Import only what you use
  • SSR safe — No browser-only APIs
  • Memoization friendly — Minimal re-renders, memo throughout
  • WCAG 2.1 accessible — All form fields include proper ARIA attributes, roles, keyboard focus, and screen reader support
  • Clean error messages — Descriptive errors with library prefix

License

MIT © 2026