Theming & Styling
Customize the look and feel of your surveys through CSS, className props, and custom components. The library is designed to be headless-friendly — you control the rendering.
Approach Overview
1. CSS Classes
The default SurveyRenderer outputs semantic HTML with BEM-style class names. Override these classes in your own stylesheet.
2. className Prop
Pass a className to SurveyRenderer or SurveyBuilder to apply Tailwind classes or custom CSS to the container.
3. Custom Components
Replace entire question renderers with your own styled components via the components prop.
4. Headless Mode
Use the useSurvey hook directly and build your own UI from scratch. Zero default styles.
CSS Class Names
The default renderer uses these class names that you can target in your CSS:
/* Container */
.rmsb-survey { }
.rmsb-survey-header { }
.rmsb-survey-title { }
.rmsb-survey-description { }
.rmsb-survey-progress { }
.rmsb-survey-progress-bar { }
/* Page */
.rmsb-page { }
.rmsb-page-title { }
.rmsb-page-description { }
/* Question */
.rmsb-question { }
.rmsb-question-label { }
.rmsb-question-description { }
.rmsb-question-error { }
.rmsb-question-required { }
/* Inputs */
.rmsb-input { }
.rmsb-textarea { }
.rmsb-select { }
.rmsb-radio-group { }
.rmsb-radio-option { }
.rmsb-checkbox-group { }
.rmsb-checkbox-option { }
/* Navigation */
.rmsb-navigation { }
.rmsb-btn-prev { }
.rmsb-btn-next { }
.rmsb-btn-submit { }
/* Complete screen */
.rmsb-complete { }className Prop
// Apply Tailwind classes to the survey container
<SurveyRenderer
schema={schema}
className="max-w-2xl mx-auto p-6 bg-white dark:bg-gray-900 rounded-xl shadow-lg"
options={{ onSubmit: handleSubmit }}
/>Overriding with Custom CSS
/* Custom theme - dark mode example */
.rmsb-survey {
background: #1a1a2e;
color: #e0e0e0;
border-radius: 16px;
padding: 2rem;
}
.rmsb-input,
.rmsb-textarea,
.rmsb-select {
background: #16213e;
border: 1px solid #0f3460;
color: #e0e0e0;
border-radius: 8px;
padding: 0.75rem 1rem;
}
.rmsb-input:focus,
.rmsb-textarea:focus {
border-color: #e94560;
outline: none;
box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.2);
}
.rmsb-question-error {
color: #e94560;
}
.rmsb-btn-next,
.rmsb-btn-submit {
background: #e94560;
color: white;
border-radius: 8px;
padding: 0.5rem 1.5rem;
}Custom Components
For full control over individual question types, provide custom components:
import type { QuestionComponentProps } from 'react-minimal-survey-builder';
function StyledTextInput({ question, value, onChange, error, disabled }: QuestionComponentProps) {
return (
<div className="mb-6">
<label className="block text-sm font-semibold text-gray-700 mb-1.5">
{question.label}
{question.required && <span className="text-red-500 ml-0.5">*</span>}
</label>
{question.description && (
<p className="text-xs text-gray-400 mb-2">{question.description}</p>
)}
<input
type="text"
value={(value as string) ?? ''}
onChange={(e) => onChange(e.target.value)}
disabled={disabled}
placeholder={question.placeholder}
className={`w-full px-4 py-3 rounded-xl border transition-colors
${error ? 'border-red-400 bg-red-50' : 'border-gray-200 bg-gray-50'}
focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent
disabled:opacity-50 disabled:cursor-not-allowed`}
/>
{error && <p className="text-red-500 text-xs mt-1.5">{error}</p>}
</div>
);
}
<SurveyRenderer
schema={schema}
components={{ text: StyledTextInput, email: StyledTextInput }}
options={{ onSubmit: handleSubmit }}
/>Fully Headless
For complete design freedom, skip SurveyRenderer and use the useSurvey hook:
import { useSurvey } from 'react-minimal-survey-builder';
function MyThemedSurvey() {
const { answers, setAnswer, getVisibleQuestions, getError, submit, progress } = useSurvey(schema, {
onSubmit: handleSubmit,
});
return (
<div className="my-custom-theme">
<div className="progress-ring" style={{ '--progress': progress } as React.CSSProperties} />
{getVisibleQuestions().map((q) => (
<div key={q.id} className="my-question-card">
<h3>{q.label}</h3>
<input
value={(answers[q.id] as string) ?? ''}
onChange={(e) => setAnswer(q.id, e.target.value)}
/>
{getError(q.id) && <span className="my-error">{getError(q.id)}</span>}
</div>
))}
<button onClick={submit} className="my-submit-btn">Submit</button>
</div>
);
}Custom Header & Footer
Use renderHeader and renderFooter to replace the default chrome while keeping the built-in question rendering:
<SurveyRenderer
schema={schema}
options={{ onSubmit: handleSubmit }}
renderHeader={({ title, progress }) => (
<div className="flex items-center justify-between mb-8">
<h1 className="text-2xl font-bold">{title}</h1>
<span className="text-sm text-gray-500">{Math.round(progress)}% complete</span>
</div>
)}
renderFooter={({ isLastPage, prevPage, nextPage, submit, hasPrevPage }) => (
<div className="flex justify-between mt-8 pt-4 border-t">
{hasPrevPage ? (
<button onClick={prevPage} className="text-gray-500 hover:text-gray-700">← Back</button>
) : <div />}
<button
onClick={isLastPage ? submit : nextPage}
className="px-6 py-2 bg-blue-600 text-white rounded-full hover:bg-blue-700"
>
{isLastPage ? 'Submit' : 'Continue →'}
</button>
</div>
)}
/>Recommendation
Start with the default SurveyRenderer and layer on custom CSS. If you need more control, swap in custom components. For a unique design, go fully headless with useSurvey.