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

@harshshahcg/survey-render

v1.2.0

Published

Reusable Vite React + TypeScript survey rendering library

Readme

survey-render

A reusable React + TypeScript survey rendering library. Renders surveys from a JSON config with conditional logic, validation, file uploads (base64), and theming. Returns responses in a structured array format. No offline sync or built-in storage—you own submission and persistence.

Features

  • JSON-driven — Define surveys via SurveyConfig; no hard-coded forms
  • Multiple question types — Single/multi select, dropdown, text, date, tabular inputs, image/video/voice/document/PDF uploads
  • Conditional logic — Show/hide questions based on parent answers (childrenQuestions / sourceQuestion)
  • Validation — Mandatory checks, character limits, numeric input, file type/size, tabular completeness
  • Media uploads — Image, video, voice, document, PDF with configurable allowed types and max size (base64 in payload)
  • Step-based UI — Carousel navigation, overview panel, previous/next/submit, optional exit with save
  • Themes — 11 built-in color themes + dark mode

Installation

npm install @harshshahcg/survey-render

Peer dependencies: React and React DOM (≥18).

npm install react react-dom

Quick Start

import { SurveyRenderer } from "@harshshahcg/survey-render";
import type { SurveyConfig, SurveySubmitPayload } from "@harshshahcg/survey-render";

const config: SurveyConfig = {
  surveyId: "my-survey",
  surveyName: "Quick Feedback",
  medium: "EN",
  questionList: [
    {
      surveyId: "my-survey",
      questionId: "Q1",
      description: "What is your name?",
      descriptionEN: "What is your name?",
      type: "text-response",
      isMandatory: true,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    {
      surveyId: "my-survey",
      questionId: "Q2",
      description: "How would you rate us?",
      descriptionEN: "How would you rate us?",
      type: "single-select",
      isMandatory: true,
      options: ["Poor", "Fair", "Good", "Excellent"],
      optionsEN: ["Poor", "Fair", "Good", "Excellent"],
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
  ],
};

function App() {
  const handleSubmit = (payload: SurveySubmitPayload) => {
    console.log("Submitted:", payload);
  };

  return (
    <SurveyRenderer
      data={config}
      onSubmit={handleSubmit}
      theme="emerald"
    />
  );
}

Full Sample Example

This example covers all major features: question types, conditional logic, media config, progress bar, themes, save/exit, and validation.

import React, { useState } from "react";
import { SurveyRenderer } from "@harshshahcg/survey-render";
import type {
  SurveyConfig,
  SurveySubmitPayload,
} from "@harshshahcg/survey-render";

const fullSampleConfig: SurveyConfig = {
  surveyId: "full-demo",
  surveyName: "Complete Survey Demo",
  medium: "EN",
  // Optional: media validation (allowed types, max size in MB)
  config: {
    "image-response": {
      suppotedType: ["image/jpeg", "image/png"],
      supportedSize: 10,
    },
    "video-response": { suppotedType: ["video/mp4"], supportedSize: 50 },
    "voice-response": { suppotedType: ["audio/mpeg"], supportedSize: 10 },
    "pdf-response": { suppotedType: ["application/pdf"], supportedSize: 10 },
  },
  questionList: [
    // Text response
    {
      surveyId: "full-demo",
      questionId: "Q1",
      description: "Your name?",
      descriptionEN: "Your name?",
      type: "text-response",
      isMandatory: true,
      textLimitCharacter: 100,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Single select
    {
      surveyId: "full-demo",
      questionId: "Q2",
      description: "Select your role",
      descriptionEN: "Select your role",
      type: "single-select",
      isMandatory: true,
      options: ["Developer", "Designer", "Manager", "Other"],
      optionsEN: ["Developer", "Designer", "Manager", "Other"],
      childrenQuestions: { Other: ["Q2.1"] },
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Conditional text (shown only when "Other" selected in Q2)
    {
      surveyId: "full-demo",
      questionId: "Q2.1",
      description: "Please specify your role",
      descriptionEN: "Please specify your role",
      type: "text-response",
      isMandatory: false,
      sourceQuestion: "Q2",
      outcomeDescription: "",
      questionDescriptionOptional: "",
    },
    // Multi-select
    {
      surveyId: "full-demo",
      questionId: "Q3",
      description: "Select all that apply",
      descriptionEN: "Select all that apply",
      type: "multi-select",
      isMandatory: false,
      options: ["Option A", "Option B", "Option C"],
      optionsEN: ["Option A", "Option B", "Option C"],
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Dropdown
    {
      surveyId: "full-demo",
      questionId: "Q4",
      description: "Choose one (dropdown)",
      descriptionEN: "Choose one (dropdown)",
      type: "drop-down",
      isMandatory: true,
      options: ["Choice 1", "Choice 2", "Choice 3"],
      optionsEN: ["Choice 1", "Choice 2", "Choice 3"],
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Date picker
    {
      surveyId: "full-demo",
      questionId: "Q5",
      description: "Date of last visit",
      descriptionEN: "Date of last visit",
      type: "calendar",
      isMandatory: false,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Image upload
    {
      surveyId: "full-demo",
      questionId: "Q6",
      description: "Upload a photo",
      descriptionEN: "Upload a photo",
      type: "image-response",
      isMandatory: false,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
    // Tabular input
    {
      surveyId: "full-demo",
      questionId: "Q7",
      description: "Fill the table",
      descriptionEN: "Fill the table",
      type: "tabular-text-input",
      isMandatory: false,
      tableHeaderValue: ["Item", "Value"],
      tableQuestionValue: [
        { rowId: "a", rowQuestion: "Field A" },
        { rowId: "b", rowQuestion: "Field B" },
      ],
      textInputType: "None",
      textLimitCharacter: 50,
      outcomeDescription: "",
      questionDescriptionOptional: "",
      sourceQuestion: "",
    },
  ],
};

export default function App() {
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = async (payload: SurveySubmitPayload) => {
    setSubmitting(true);
    try {
      console.log("Submit:", payload);
      await fetch("/api/survey", {
        method: "POST",
        body: JSON.stringify(payload),
        headers: { "Content-Type": "application/json" },
      });
      alert("Thank you! Survey submitted.");
    } finally {
      setSubmitting(false);
    }
  };

  const handleSave = (payload: SurveySubmitPayload) => {
    localStorage.setItem("survey-draft", JSON.stringify(payload));
    console.log("Draft saved");
  };

  const handleExit = () => {
    if (window.confirm("Leave without submitting?")) window.history.back();
  };

  return (
    <SurveyRenderer
      data={fullSampleConfig}
      onSubmit={handleSubmit}
      onSave={handleSave}
      onExit={handleExit}
      isSubmitting={submitting}
      showHeader={true}
      theme="emerald"
      className="min-h-screen max-w-2xl mx-auto p-4"
      progressBar={{
        showLabel: true,
        showPercentage: true,
        labelFormat: "Question {current} of {total}",
        variant: "default",
        progressMode: "completion",
      }}
    />
  );
}

Themes

Apply a theme by passing the theme prop to SurveyRenderer. Wrap your app (or a parent) with data-theme="..." if you manage the DOM. The library sets it when theme is provided.

| Theme | Sample | Primary Color | |-----------|-------------------------------------|-----------------| | emerald | | #059669 | | violet | | #7c3aed | | amber | | #d97706 | | blue | | #2563eb | | rose | | #e11d48 | | teal | | #0d9488 | | indigo | | #4f46e5 | | cyan | | #0891b2 | | orange | | #ea580c | | lime | | #65a30d | | fuchsia | | #c026d3 | | dark | | Dark mode variant |

<SurveyRenderer data={config} onSubmit={onSubmit} theme="violet" />

Build

Build the library for distribution:

npm run build

This produces:

  • dist/surveyRender.js — ESM bundle
  • dist/surveyRender.umd.cjs — UMD bundle
  • dist/index.d.ts — TypeScript declarations

Publishing to npm

  1. Create an npm account at npmjs.com if needed.

  2. Log in from the project directory:

    npm login
  3. Check package name availability. If survey-render is taken, use a scoped name in package.json:

    "name": "@your-username/survey-render"
  4. Build and publish:

    npm run build
    npm publish

    The prepublishOnly script runs npm run build automatically before npm publish.

  5. For scoped packages, publish with public access:

    npm publish --access public
  6. Version updates:

    npm version patch   # 1.0.0 → 1.0.1
    npm version minor   # 1.0.0 → 1.1.0
    npm publish

How to Import

// Main component + types
import { SurveyRenderer } from "@harshshahcg/survey-render";
import type {
  SurveyConfig,
  SurveySubmitPayload,
  SurveyQuestion,
} from "@harshshahcg/survey-render";

// Multiple components (custom layouts)
import {
  SurveyRenderer,
  SurveyContainer,
  QuestionRenderer,
  ValidationDialog,
  ExitConfirmationDialog,
} from "@harshshahcg/survey-render";

// Utils
import {
  validateResponses,
  canSubmit,
  formatResponses,
  calculateVisibility,
  getQuestionStatusMap,
  calculateWeightedProgress,
  isValueFileObject,
  getEffectiveMediaConfig,
  SUPPORTED_QUESTION_TYPES,
  isSupportedQuestionType,
} from "@harshshahcg/survey-render";

Styling: The library uses Tailwind CSS. Your app must have Tailwind configured so the survey UI renders correctly.


Survey Config Shape

  • SurveyConfigsurveyId, medium, questionList (required). Optional: surveyCode, surveyName, config (media validation).
  • SurveyQuestionsurveyId, questionId, description, descriptionEN, type, isMandatory, outcomeDescription, questionDescriptionOptional, sourceQuestion. Optional: options, optionsEN, textInputType, textLimitCharacter, minValue, maxValue, childrenQuestions, tableHeaderValue, tableQuestionValue, userAnswer.
  • Media config (data.config) — Keys: "image-response", "video-response", "voice-response", "document-response", "pdf-response". Values: supportedType (or legacy suppotedType) and supportedSize (MB).

Supported Question Types

| Type | Description | |---------------------|--------------------------| | single-select | One option from list | | multi-select | Multiple options | | drop-down | Dropdown single select | | text-response | Free text (optional limit) | | calendar | Date picker | | image-response | Image upload (base64) | | video-response | Video upload (base64) | | voice-response | Audio upload (base64) | | document-response | Document upload (base64) | | pdf-response | PDF upload (base64) | | tabular-text-input| Rows with text fields | | tabular-drop-down | Rows with dropdowns | | tabular-check-box | Rows with checkboxes |


Conditional Logic

  • Root questions — No sourceQuestion; always visible.
  • Child questions — Listed in another question’s childrenQuestions. Shown when the parent’s answer matches the key (e.g. "Other": ["Q5.1"] → show Q5.1 when parent answer is "Other").
  • Hidden questions’ answers are cleared when they become hidden.

Helpers: calculateVisibility, clearHiddenResponses.


Validation

Mandatory, text limit, numeric input type, file presence, tabular completeness. On submit, invalid answers produce a list of { questionId, message } and the built-in validation dialog opens. Helpers: validateResponses, canSubmit.


Response Format

SurveySubmitPayload — Passed to onSubmit and onSave: surveyId, optional surveyCode/surveyName/type, and responses: SurveyResponseArray.

SurveyResponseArray — Array of { questionId, userAnswer }. userAnswer is string, number, boolean, string[], RowAnswer[] (tabular), or FileObject (fileName, fileType, binary base64). Dates are formatted as YYYY-MM-DD.


License

MIT — see LICENSE.