@harshshahcg/survey-render
v1.2.0
Published
Reusable Vite React + TypeScript survey rendering library
Maintainers
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-renderPeer dependencies: React and React DOM (≥18).
npm install react react-domQuick 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 buildThis produces:
dist/surveyRender.js— ESM bundledist/surveyRender.umd.cjs— UMD bundledist/index.d.ts— TypeScript declarations
Publishing to npm
Create an npm account at npmjs.com if needed.
Log in from the project directory:
npm loginCheck package name availability. If
survey-renderis taken, use a scoped name inpackage.json:"name": "@your-username/survey-render"Build and publish:
npm run build npm publishThe
prepublishOnlyscript runsnpm run buildautomatically beforenpm publish.For scoped packages, publish with public access:
npm publish --access publicVersion 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
SurveyConfig—surveyId,medium,questionList(required). Optional:surveyCode,surveyName,config(media validation).SurveyQuestion—surveyId,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 legacysuppotedType) andsupportedSize(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.
