WebberUI

Chromatic Variant Stage

A product display stage where picking a swatch performs three transitions at once — the background bleeds outward radially, the product image cross-dissolves, and the price and model number flip to their new values.

Loading preview…
npx shadcn@latest add https://webberui.com/r/chromatic-variant-stage.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

0.7
<ChromaticVariantStage />

Installation

npx shadcn@latest add https://webberui.com/r/chromatic-variant-stage.json

Or, once registries are configured in components.json, install it as @webberui/chromatic-variant-stage.

Usage

import {
  ChromaticVariantStage,
  type ChromaticVariant,
} from "@/components/ui/chromatic-variant-stage";

const variants: ChromaticVariant[] = [
  { id: "coral", name: "Coral", color: "#ff6a55", price: "$269", model: "WB-900 / CR" },
  { id: "azure", name: "Azure", color: "#4f8ff0", price: "$289", model: "WB-900 / AZ" },
];

<ChromaticVariantStage title="Webber wireless headphones" variants={variants} defaultValue="coral" />

When no image is provided, a default product silhouette is generated automatically in the variant's color; to swap in a photograph or an SVG, just put the node in each variant's image field.

Controlled mode

Pass value and onValueChange to drive the selected variant from the outside:

const [color, setColor] = React.useState("coral");

<ChromaticVariantStage
  variants={variants}
  value={color}
  onValueChange={setColor}
/>

Props

PropTypeDefaultDescription
variantsChromaticVariant[]All selectable variants; their order is the order of the swatches
valuestringControlled: id of the currently selected variant
defaultValuestringFirst variantInitial variant id in uncontrolled mode
onValueChange(id: string) => voidFires when the selected variant changes
titlestringTitle above the stage
swatchLabelstring"Choose a color"Accessible label for the swatch group
durationnumber0.7Duration of the triple transition (seconds)

ChromaticVariant

FieldTypeDescription
idstringUnique identifier (used for both controlled and uncontrolled switching)
namestringVariant name: the swatch's aria-label and what gets announced
colorstringThe variant's main color (any CSS color value): the swatch, the radial bleed, and the stage background are all derived from it
pricestringDisplayed price (including the currency symbol, such as $129)
modelstringModel / style code
imageReact.ReactNodeProduct visual; when not provided, a default silhouette generated in the variant color is shown

How it works

  • Triple transition: a single click triggers three things at once — the background bleed, the product image cross-dissolve (AnimatePresence overlaps a fade in and out with a slight scale), and the flip that updates the price and model number (a rotateX flip around the X axis, with the price flipping up from the bottom edge and the model down from the top). All three share the same duration and easing, so they read as three facets of one motion.
  • Anchoring the radial bleed's origin: clicking a swatch measures that swatch's center relative to the stage in real time and converts it to percentages to use as the radial origin; the color therefore bleeds out from the swatch you pressed, rather than always spreading from the center. Moving the selection with the keyboard uses the corresponding swatch's position as the origin in exactly the same way.
  • A handoff with no color jump: the bleed layer and the base color layer use the same radial background function, and the bleed expands with clip-path: circle() from 0% to 150% (150% guarantees any origin can cover the whole rectangle). At the moment it is fully open, the bleed layer and the base color are pixel-identical, so when the animation ends the base color is advanced to the new variant and the bleed layer is removed — with no flicker at the handoff.

Accessibility

  • The swatch group is a role="radiogroup" and each swatch a role="radio" marking its selected state with aria-checked; it uses a roving tabindex (only the selected swatch can be reached with Tab).
  • Keyboard operation: the arrow keys move the selection between swatches and focus follows automatically, while Home / End jump to the first and last.
  • The price block carries aria-live="polite", so the updated price is announced when the variant changes; the default product silhouette carries role="img" and an aria-label.
  • When the user has "reduce motion" enabled at the system level, the background switches directly with no bleed and the product image and text update instantly, with no transitions played.

On this page