Theming & Styling
CSS custom properties, per-element class overrides, and fully unstyled mode.
Theming & Styling
There are three levels, from smallest change to largest:
themetokens — recolour the bundled look.classNames— add your own classes alongside the bundled ones.unstyled— drop the bundled look entirely and bring your own CSS.
Theme tokens
v1.2The 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"}Themed wizard
Live CSS custom properties
<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 token | CSS custom property | Applies to |
|---|---|---|
primaryColor | --rfw-primary | Active tabs, progress, buttons |
backgroundColor | --rfw-bg | Step marker surface |
textColor | --rfw-text | Default and disabled text |
titleColor | --rfw-title | Wizard title |
subtitleColor | --rfw-subtitle | Subtitle / category line |
tabColor | --rfw-tab | Inactive tab hover surface |
tabIconColor | --rfw-tab-icon | Icon inside tab circles |
borderColor | --rfw-border | Navigation rail and marker borders |
buttonColor | --rfw-button | Back / Next background |
buttonTextColor | --rfw-button-text | Back / Next label |
finishButtonColor | --rfw-finish-button | Finish background |
finishButtonTextColor | --rfw-finish-button-text | Finish label |
errorColor | --rfw-error | Invalid step marker and message |
borderRadius | --rfw-radius | Buttons 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.2classNames 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.2unstyled drops every bundled class and every inline colour, so your
classes are the only styling. Combine it with classNames:
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
v2The 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:
- Your app's own switch — an ancestor carrying
[data-theme="dark"]or a.darkclass. That covers Tailwind, next-themes, Docusaurus and Fumadocs without configuration. - The
darkModeprop, 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:
| Class | On |
|---|---|
active | the step the user is on |
rfw-done | steps already completed |
rfw-invalid | a 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
v2v2 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 markers | 36 px, hairline border, filled when complete | 70 px filled circles |
| Colour applied via | CSS custom properties and state classes | inline styles |
| Dark mode | automatic | darkMode + customDarkModeColor only |
| Your CSS can override it | yes | only with !important |
| Stylesheet | 1.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.