S
Documentation

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:

CSS Class Referencecss
/* 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

className Proptsx
// 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

Dark Theme Examplecss
/* 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:

Custom Styled Componenttsx
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:

Headless Approachtsx
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:

Custom Header & Footertsx
<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.