react-variant-switcher lets you preview UI alternatives by wrapping variant blocks with declarative React components and switching between them from a floating control.
- Provider + group + variant-option API
- Floating switcher UI with previous/next controls
- Keyboard shortcuts (
Alt+Arrowto switch,Cmd+Hto toggle switcher,Alt+Sto cycle groups) localStoragepersistence- Optional URL query syncing (
?groupName=optionId) - Fully themeable via CSS custom properties and class overrides
pnpm install react-variant-switcher -DImport the stylesheet in your app entry:
import "react-variant-switcher/styles.css";import {
VariantProvider,
VariantGroup,
VariantOption
} from "react-variant-switcher";
export function App() {
return (
<VariantProvider syncWithUrl>
<VariantGroup name="hero">
<VariantOption name="centered" label="Centered quote" default>
<section>Centered quote version</section>
</VariantOption>
<VariantOption name="left" label="Left quote">
<section>Left aligned version</section>
</VariantOption>
</VariantGroup>
</VariantProvider>
);
}type VariantProviderProps = {
children: ReactNode;
disabled?: boolean;
defaultGroupId?: string;
showSwitcher?: boolean;
disablePersistence?: boolean;
syncWithUrl?: boolean;
disableKeyboardShortcuts?: boolean;
};| Prop | Default | Description |
|---|---|---|
children |
β | Your app content. Place VariantGroup trees anywhere inside. |
disabled |
false |
Disables the entire switcher. All groups render only their default/active option and the switcher UI is hidden. Useful for production builds. |
defaultGroupId |
β | The internal ID of the group that should be focused in the switcher on first render. |
showSwitcher |
true in dev, false in prod |
Overrides switcher visibility. Pass false to always hide it, true to always show it regardless of environment. |
disablePersistence |
false |
Disables localStorage persistence. By default, active selections are saved and restored on reload. |
syncWithUrl |
false |
Syncs active selections to URL query params (e.g. ?hero=centered). Useful for sharing specific variants via link. |
disableKeyboardShortcuts |
false |
Disables all keyboard shortcuts (Alt+Arrow, Cmd+H, Alt+S). |
type VariantGroupProps = {
name: string;
disabled?: boolean;
children: ReactNode;
};| Prop | Default | Description |
|---|---|---|
name |
β | Unique name for this group. Used as the URL param key when syncWithUrl is on and as the argument to useVariant(name). |
disabled |
false |
Renders only the default/active option in this group. The group is hidden from the switcher UI. |
children |
β | VariantOption elements. |
type VariantOptionProps = {
name: string;
label?: string;
default?: boolean;
disabled?: boolean;
children: ReactNode;
};| Prop | Default | Description |
|---|---|---|
name |
β | Unique identifier for this option within its group. Used as the URL param value when syncWithUrl is on. |
label |
same as name |
Display label shown in the switcher dropdown. Falls back to name if omitted. |
default |
false |
Marks this option as the default selection. If multiple options have default, the first one in declaration order wins. If no option has default, the first option is selected. |
disabled |
false |
Excludes this option from registration entirely. It won't appear in the switcher and its children won't render. |
children |
β | Content rendered when this option is active. |
VariantProvider disabledβ renders only the default/active option and hides the switcher.VariantGroup disabledβ renders only the default/active option in that group.VariantOption disabledβ excludes the option from registration and renders nothing.
Pass the same name string you used on <VariantGroup name="...">.
Returns:
groupName,groupId,options,activeOptionName,activeOption- actions:
setActive(optionName),next(),previous(),focus()
A standalone switcher component you can place anywhere inside a VariantProvider. When rendered, it suppresses the automatic floating switcher.
import { VariantSwitcher } from "react-variant-switcher";
<VariantSwitcher className="my-custom-class" style={{ bottom: 40 }} />Variables are scoped to .rvs-root and use shadcn/ui-compatible names. Override them on .rvs-root or any parent element:
/* Switcher (scoped to .rvs-root) */
--background /* switcher background */
--foreground /* primary text */
--muted /* hover bg, dividers */
--muted-foreground /* secondary text, counters, icons */
--accent /* select/badge surface */
--accent-foreground /* hover text */
--border /* switcher border */
--radius /* border radius (default: pill) */
--shadow /* box shadow */
--font-sans /* font family */
--font-mono /* counter font family */
--font-size /* base font size */
--font-size-counter /* counter font size */
/* Overlay (scoped to .rvs-overlay) */
--popover /* overlay background */
--popover-foreground /* overlay text */
--popover-border /* overlay border */
--popover-shadow /* overlay shadow */
--popover-highlight /* preview highlight bg */
--popover-current /* current group text */
--popover-dim /* inactive group text */If you use shadcn/ui, the switcher inherits your theme automatically when you override these variables at a parent level.
All switcher elements use .rvs- prefixed classes that you can override:
| Class | Element |
|---|---|
.rvs-root |
Switcher container (pill) |
.rvs-portal |
Fixed-position portal wrapper |
.rvs-nav-btn |
Previous/next arrow buttons |
.rvs-divider |
Vertical separator lines |
.rvs-select-label |
Select wrapper <label> |
.rvs-select |
Dropdown <select> elements |
.rvs-select-with-prefix |
Select with counter prefix (extra left padding) |
.rvs-select-chevron |
Dropdown arrow icon |
.rvs-counter |
Option index counter (e.g. "2/3") |
.rvs-badge |
Single-option label (no dropdown) |
.rvs-overlay |
Group switcher overlay container |
.rvs-overlay-portal |
Overlay portal wrapper |
.rvs-overlay-option |
Individual group option row |
.rvs-overlay-option-current |
Currently active group row |
A runnable Vite example is included in examples/spacex.
pnpm install
pnpm run lint
pnpm run test
pnpm run buildMIT