React Form Wizard
Guides

Theming & Styling

CSS custom properties, per-element class overrides, and fully unstyled mode.

Theming & Styling

There are three levels, from smallest change to largest:

  1. theme tokens — recolour the bundled look.
  2. classNames — add your own classes alongside the bundled ones.
  3. unstyled — drop the bundled look entirely and bring your own CSS.

Theme tokens

v1.2

The theme prop writes --rfw-* CSS custom properties onto the wizard root. The stylesheet reads them with fallbacks, so you can override one value without restating a palette.

{"primaryColor":"var(--color-fd-primary)","borderRadius":"10px","errorColor":"#c0392b"}
Form wizard with 3 steps. Currently on step 1.

Themed wizard

Live CSS custom properties

Step 1 of 3: One

The accent, progress ring and buttons all read the same token.

<FormWizard
  theme={{
    primaryColor: "#0e6f70",
    backgroundColor: "#ffffff",
    errorColor: "#c0392b",
    borderRadius: "8px",
  }}
/>

Or set the same variables in your own stylesheet for a global default:

.react-form-wizard {
  --rfw-primary: #0e6f70;
  --rfw-radius: 8px;
}

Tokens

Prop tokenCSS custom propertyApplies to
primaryColor--rfw-primaryActive tabs, progress, buttons
backgroundColor--rfw-bgStep marker surface
textColor--rfw-textDefault and disabled text
titleColor--rfw-titleWizard title
subtitleColor--rfw-subtitleSubtitle / category line
tabColor--rfw-tabInactive tab hover surface
tabIconColor--rfw-tab-iconIcon inside tab circles
borderColor--rfw-borderNavigation rail and marker borders
buttonColor--rfw-buttonBack / Next background
buttonTextColor--rfw-button-textBack / Next label
finishButtonColor--rfw-finish-buttonFinish background
finishButtonTextColor--rfw-finish-button-textFinish label
errorColor--rfw-errorInvalid step marker and message
borderRadius--rfw-radiusButtons and square markers

customDarkModeColor only applies when darkMode is on. Theme tokens apply in both light and dark, and are plain CSS variables — so media queries, a data-theme attribute, or a class toggle can all drive them.

Class overrides

v1.2

classNames adds your classes to the bundled ones:

<FormWizard classNames={{ root: "my-wizard", nextButton: "my-cta" }} />

Keys: root, header, title, subtitle, navigation, stepList, step, stepActive, stepIcon, stepTitle, content, footer, backButton, nextButton, finishButton.

Unstyled mode

v1.2

unstyled drops every bundled class and every inline colour, so your classes are the only styling. Combine it with classNames:

Form wizard with 3 steps. Currently on step 1.
Step 1 of 3: Details

No bundled CSS is applied here — every class is mine.

<FormWizard
  unstyled
  classNames={{
    root: "flex flex-col gap-6",
    stepList: "flex gap-2",
    step: "px-3 py-1 rounded text-slate-500",
    stepActive: "bg-teal-700 text-white",
    content: "rounded-lg border p-6",
    footer: "mt-6 flex justify-between",
    backButton: "rounded px-4 py-2 text-slate-600 hover:bg-slate-100",
    nextButton: "rounded bg-teal-700 px-4 py-2 text-white",
    finishButton: "rounded bg-teal-700 px-4 py-2 text-white",
  }}
/>

In unstyled mode you can skip import "react-form-wizard-component/styles.css" entirely. Screen-reader-only text stays hidden without it, so accessibility is unaffected.

What unstyled keeps

Structure and semantics only — the tablist / tab / tabpanel roles, ARIA wiring, live-region announcements, focus management and keyboard navigation all still work. It removes appearance, not behaviour.

Dark mode

v2

The wizard ships a real dark palette and picks it up on its own — but it follows the page, not the operating system.

By default (colorScheme="auto") it reacts to:

  1. Your app's own switch — an ancestor carrying [data-theme="dark"] or a .dark class. That covers Tailwind, next-themes, Docusaurus and Fumadocs without configuration.
  2. The darkMode prop, which pins it explicitly.

Because a component embedded in someone else's layout cannot assume the OS preference describes the surface behind it. A light page on a dark-mode machine would get a dark wizard on a white background — which is exactly what an early v2 build did.

If your page does drive its theme purely from the media query, opt in:

<FormWizard colorScheme="system" />

Usually there is nothing to do:

<FormWizard schema={schema} />

Pin it when you need to:

<FormWizard colorScheme="dark" />   {/* always dark */}
<FormWizard colorScheme="light" />  {/* opt out of the OS preference */}
<FormWizard colorScheme="auto" />   {/* the default */}

To adjust the dark palette, override the tokens under your own selector:

.dark .react-form-wizard {
  --rfw-primary: #7dd3fc;
  --rfw-surface: #0b1220;
}

Through v1 the stylesheet had no dark rules at all. Dark mode existed only as inline styles you enumerated through customDarkModeColor, and because they were inline they overrode any CSS you wrote. customDarkModeColor still works, but theme tokens are the better tool.

State classes

The modern skin puts a class on each step so your own CSS can target state without reaching for unstyled:

ClassOn
activethe step the user is on
rfw-donesteps already completed
rfw-invalida step that failed validation after an attempt to leave it
.react-form-wizard li.rfw-done .wizard-icon-circle {
  background: var(--rfw-success);
  border-color: var(--rfw-success);
}

In unstyled mode the equivalents are the stepActive, stepComplete and stepInvalid keys of classNames.

rfw-invalid is applied only once the user has actually tried to leave the step. Through v1 any step carrying a validator rendered red on first paint, which made a required first question look like a broken form. Pass showErrorOnTab explicitly if you want the old, immediate behaviour.

Legacy skin

v2

v2 changed the default appearance. If you styled around the old look — or simply prefer it — it is one import and one prop away:

import "react-form-wizard-component/styles.css";
import "react-form-wizard-component/legacy.css";

<FormWizard variant="legacy" schema={schema} />;

The legacy stylesheet is shipped separately so sites on the modern skin do not download CSS they never use.

Modern (default)Legacy
Step markers36 px, hairline border, filled when complete70 px filled circles
Colour applied viaCSS custom properties and state classesinline styles
Dark modeautomaticdarkMode + customDarkModeColor only
Your CSS can override ityesonly with !important
Stylesheet1.4 kB brotli+1.8 kB brotli

The legacy skin paints colours inline, so a rule in your own stylesheet will lose to it unless you use !important. That is the main reason the modern skin exists — and why theme is more reliable than the color prop.

See also: dark mode demo, Props.

On this page