Custom Question Types
Extend the library with custom question types by providing your own React components. Use the components prop or the plugin system to register them.
QuestionComponentProps
Every custom question component receives a standard props interface:
QuestionComponentPropstypescript
type QuestionComponentProps<V = AnswerValue> = {
question: Question; // The question schema object
value: V; // Current answer value
onChange: (value: V) => void; // Update the answer
error?: string; // Validation error message, if any
disabled?: boolean; // Whether the input should be disabled
};QuestionComponents Map
QuestionComponentstypescript
type QuestionComponents = Partial<
Record<QuestionType, React.ComponentType<QuestionComponentProps>>
>;
// QuestionType = 'text' | 'textarea' | 'radio' | 'checkbox' | 'select'
// | 'number' | 'email' | 'date' | 'rating' | stringCreating a Custom Component
Build a component that implements QuestionComponentProps:
Custom Star Ratingtsx
import type { QuestionComponentProps } from 'react-minimal-survey-builder';
function StarRating({ question, value, onChange, error, disabled, questionNumber }: QuestionComponentProps) {
const stars = [1, 2, 3, 4, 5];
const current = (value as number) ?? 0;
return (
<div>
<label className="block font-medium mb-2">
{questionNumber !== undefined && (
<span className="text-gray-500 mr-1">{questionNumber}.</span>
)}
{question.label}
{question.required && <span className="text-red-500"> *</span>}
</label>
{question.description && (
<p className="text-sm text-gray-500 mb-2">{question.description}</p>
)}
<div className="flex gap-1">
{stars.map((star) => (
<button
key={star}
type="button"
disabled={disabled}
onClick={() => onChange(star)}
className={star <= current ? 'text-yellow-400' : 'text-gray-300'}
>
★
</button>
))}
</div>
{error && <p className="text-red-500 text-sm mt-1">{error}</p>}
</div>
);
}Registering via components Prop
Pass custom components to SurveyRenderer using the components prop:
Components Proptsx
import { SurveyRenderer } from 'react-minimal-survey-builder';
function App() {
return (
<SurveyRenderer
schema={schema}
components={{
rating: StarRating, // override built-in rating
'color-picker': ColorPicker, // register a brand-new type
'file-upload': FileUpload,
}}
options={{ onSubmit: handleSubmit }}
/>
);
}Using Custom Types in Schema
Use any string as the question type. It will be matched against the components map:
Schema with Custom Typestypescript
const schema: SurveySchema = {
id: 'custom-demo',
pages: [
{
id: 'page1',
questions: [
{
id: 'fav-color',
type: 'color-picker', // matches components['color-picker']
label: 'Pick your favorite color',
meta: { palette: ['red', 'blue', 'green', 'yellow'] },
},
{
id: 'resume',
type: 'file-upload', // matches components['file-upload']
label: 'Upload your resume',
meta: { accept: '.pdf,.doc,.docx', maxSizeMB: 5 },
},
],
},
],
};Using the meta Field
The meta field on a question allows you to pass arbitrary data to custom components:
Reading meta in a Componenttsx
function ColorPicker({ question, value, onChange }: QuestionComponentProps) {
const palette = (question.meta?.palette as string[]) ?? ['#000', '#fff'];
return (
<div>
<label>{question.label}</label>
<div className="flex gap-2">
{palette.map((color) => (
<button
key={color}
type="button"
onClick={() => onChange(color)}
style={{
width: 32, height: 32,
background: color,
border: value === color ? '3px solid blue' : '1px solid gray',
borderRadius: 6,
}}
/>
))}
</div>
</div>
);
}Plugin System
For reusable packages of custom types and validators, use the plugin interface:
SurveyPlugintypescript
import type { SurveyPlugin } from 'react-minimal-survey-builder';
const fileUploadPlugin: SurveyPlugin = {
name: 'file-upload',
questionTypes: {
'file-upload': FileUploadComponent,
},
validators: {
'file-upload': (value, question) => {
const maxSize = (question.meta?.maxSizeMB as number) ?? 10;
// Custom validation logic
return null;
},
},
};Plugin Interface
SurveyPlugin Interfacetypescript
interface SurveyPlugin {
name: string;
/** Register custom question types */
questionTypes?: Record<string, React.ComponentType<QuestionComponentProps>>;
/** Extend validation */
validators?: Record<string, (value: AnswerValue, question: Question, answers: SurveyAnswers) => string | null>;
/** Middleware to intercept events */
middleware?: (event: SurveyEvent, next: () => void) => void;
}Best Practices
- - Always handle the
disabledprop to support read-only surveys. - - Display
errorwhen present for consistent validation UX. - - Use
metafor component-specific configuration rather than overloading standard fields. - - Keep custom components stateless — let the survey engine manage state via
value/onChange.