SurveyRenderer
A drop-in component that renders a complete survey from a schema. It handles rendering, navigation, validation, and submission out-of-the-box with built-in WCAG 2.1 accessibility — while still giving you full control through render props and custom components.
Import
import { SurveyRenderer } from 'react-minimal-survey-builder';Props
interface SurveyRendererProps {
/** The survey JSON schema */
schema: SurveySchema;
/** Survey options (callbacks, initial answers, etc.) */
options?: SurveyOptions;
/** Custom component overrides by question type */
components?: QuestionComponents;
/** Custom class name for the survey container */
className?: string;
/** Render prop for full control over layout */
children?: (survey: UseSurveyReturn) => React.ReactNode;
/** Disable all inputs */
disabled?: boolean;
/** Custom header renderer */
renderHeader?: (props: {
title?: string;
description?: string;
progress: number;
currentPage: number;
totalPages: number;
}) => React.ReactNode;
/** Custom footer/navigation renderer */
renderFooter?: (props: {
hasPrevPage: boolean;
hasNextPage: boolean;
isLastPage: boolean;
prevPage: () => void;
nextPage: () => void;
submit: () => void;
isValid: boolean;
}) => React.ReactNode;
/** Custom success/thank-you screen */
renderComplete?: () => React.ReactNode;
}schemaThe survey schema object that defines pages, questions, validation, and settings.
optionsCallbacks and configuration: onSubmit, onChange, initialAnswers, validators, etc.
componentsMap of custom React components keyed by question type. Overrides default rendering.
classNameCSS class applied to the outermost survey container element.
childrenRender prop that receives the full useSurvey return object for total layout control.
disabledWhen true, all inputs are disabled. Useful for read-only or review modes.
renderHeaderReplaces the default header. Receives title, description, progress, current/total pages.
renderFooterReplaces the default navigation footer. Receives navigation functions and state.
renderCompleteCustom completion/thank-you screen shown after submission.
Basic Usage
import { SurveyRenderer } from 'react-minimal-survey-builder';
import type { SurveySchema } from 'react-minimal-survey-builder';
const schema: SurveySchema = {
id: 'demo',
pages: [
{
id: 'page1',
questions: [
{ id: 'name', type: 'text', label: 'Your name', required: true },
{ id: 'email', type: 'email', label: 'Email', required: true },
],
},
],
};
function App() {
return (
<SurveyRenderer
schema={schema}
options={{
onSubmit: async (answers) => {
console.log('Submitted:', answers);
},
}}
/>
);
}Custom Components
Override the rendering of any question type by passing custom components:
import { SurveyRenderer } from 'react-minimal-survey-builder';
import type { QuestionComponentProps } from 'react-minimal-survey-builder';
function CustomTextInput({ question, value, onChange, error }: QuestionComponentProps) {
return (
<div className="my-custom-field">
<label className="my-label">{question.label}</label>
<input
className="my-input"
value={(value as string) ?? ''}
onChange={(e) => onChange(e.target.value)}
placeholder={question.placeholder}
/>
{error && <p className="my-error">{error}</p>}
</div>
);
}
<SurveyRenderer
schema={schema}
components={{
text: CustomTextInput,
email: CustomTextInput,
}}
options={{ onSubmit: (answers) => console.log(answers) }}
/>Render Prop
For full control over layout, use the children render prop. It receives the complete useSurvey return object:
<SurveyRenderer schema={schema} options={{ onSubmit: handleSubmit }}>
{(survey) => (
<div className="my-survey-layout">
<h1>{survey.survey.title}</h1>
<p>Progress: {survey.progress}%</p>
{survey.getVisibleQuestions().map((q) => (
<div key={q.id}>
<label>{q.label}</label>
<input
value={(survey.answers[q.id] as string) ?? ''}
onChange={(e) => survey.setAnswer(q.id, e.target.value)}
/>
{survey.getError(q.id) && <span>{survey.getError(q.id)}</span>}
</div>
))}
<button onClick={survey.submit}>Submit</button>
</div>
)}
</SurveyRenderer>Custom Header & Footer
<SurveyRenderer
schema={schema}
options={{ onSubmit: handleSubmit }}
renderHeader={({ title, progress, currentPage, totalPages }) => (
<div className="my-header">
<h2>{title}</h2>
<div className="progress-bar" style={{ width: `${progress}%` }} />
<span>Page {currentPage + 1} of {totalPages}</span>
</div>
)}
renderFooter={({ hasPrevPage, isLastPage, prevPage, nextPage, submit }) => (
<div className="my-footer">
{hasPrevPage && <button onClick={prevPage}>Back</button>}
{isLastPage
? <button onClick={submit}>Send</button>
: <button onClick={nextPage}>Continue</button>}
</div>
)}
renderComplete={() => (
<div className="thank-you">
<h2>🎉 Thank you!</h2>
<p>Your response has been recorded.</p>
</div>
)}
/>Disabled Mode
Pass disabled to render the survey in read-only mode. All inputs will be disabled:
<SurveyRenderer
schema={schema}
options={{ initialAnswers: savedAnswers }}
disabled
/>Accessibility
All built-in question components are fully WCAG 2.1 AA compliant out of the box. No extra configuration needed.
aria-required & requiredNative required attributes on all form inputs so screen readers announce mandatory fields.
aria-invalid & aria-describedbyInvalid fields are announced with linked error messages for assistive technologies.
Proper ARIA rolesRadio groups use role="radiogroup", checkbox groups use role="group", rating uses role="radiogroup" with aria-checked.
aria-labelledbyAll grouped inputs (radio, checkbox, boolean, rating) are linked to their visible label via aria-labelledby.
Keyboard focus-visibleScoped :focus-visible outlines for all interactive elements — inputs, buttons, radios, checkboxes, and sliders.
aria-live on errorsValidation errors use role="alert" and aria-live="assertive" so they are announced immediately by screen readers.
aria-valuemin/max/now/textSliders expose their current value, range, and unit text to assistive technologies.
Descriptive aria-labelsDate, time, datetime, phone, and slider inputs include descriptive aria-label text for context.
Tip
If you need even more control, skip SurveyRenderer entirely and use the useSurvey hook directly. The renderer is built on top of the same hook.