Anchored Buy Rail
An anchored buy rail — a long-scrolling left column beside a sticky buy panel that FLIPs down into a mini checkout capsule as you scroll, then expands again as the reviews section comes near.
This is a WebberUI Pro component
Free during the launch campaign: sign up or sign in, then hit “Copy install command” in the preview above and it installs straight away — no payment, no credit card. The command below returns 401 while you are signed out.
npx shadcn@latest add "https://webberui.com/r/anchored-buy-rail.json?t=<install token>"Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<AnchoredBuyRail />
Installation
npx shadcn@latest add "https://webberui.com/r/anchored-buy-rail.json?t=<install token>"Or, once registries are configured in components.json, install it as @webberui/anchored-buy-rail.
Usage
import { AnchoredBuyRail } from "@/components/ui/anchored-buy-rail";
<AnchoredBuyRail
title="Terra organic cotton sweatshirt"
price={1280}
compareAtPrice={1680}
currency="NT$"
variants={[
{
id: "size",
label: "Size",
options: [
{ value: "s", label: "S" },
{ value: "m", label: "M" },
{ value: "l", label: "L" },
],
},
]}
onAddToCart={(selection) => console.log(selection)}
footer={<Reviews />}
>
<ProductStory />
</AnchoredBuyRail>Put freely scrolling children (media and description blocks) in the left column; the buy panel on the right is driven by props and follows along stickily on its own. If the component is placed inside a nested overflow scroll container, pass that container's ref to container.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Product name |
price | number | — | Price, formatted together with currency |
compareAtPrice | number | — | Original price (shown struck through) |
currency | string | "$" | Currency symbol |
thumbnail | ReactNode | — | Product thumbnail, which FLIPs between the panel and the capsule |
subtitle | string | — | Subtitle; shown in the capsule when there are no variants |
variants | BuyVariantGroup[] | — | Single-select variant groups (size, color, …) |
value | BuySelection | — | Controlled selection value |
defaultValue | BuySelection | First available option in each group | Uncontrolled initial selection value |
onValueChange | (v: BuySelection) => void | — | Callback when the selection changes |
onAddToCart | (v: BuySelection) => void | — | Callback when the CTA is pressed |
ctaLabel | string | "Add to cart" | CTA copy |
note | ReactNode | Free shipping message | Trust message at the bottom of the panel |
children | ReactNode | — | Freely scrolling content in the left column |
footer | ReactNode | — | Footer block (the reviews section); the panel expands as it comes near |
container | RefObject<HTMLElement | null> | Window | Ref of the nested scroll container |
stickyTop | number | 16 | Sticky pinning position (px) |
condenseAfter | number | 96 | How many px past the top it must scroll before condensing into the capsule |
expandMargin | number | 160 | How many px from the footer it expands early |
BuyVariantGroup / BuyOption
interface BuyVariantGroup {
id: string;
label: string;
options: { value: string; label: string; disabled?: boolean }[];
}
type BuySelection = Record<string, string>; // { [group id]: option value }How it works
- Sticky + FLIP: the right column follows the scroll with
position: sticky; the price, thumbnail, and CTA carry the samelayoutId, so as it condenses and expands, Motion's shared layout animation (FLIP) tweens continuously between the full panel and the capsule. - When it condenses: two sentinels plus an
IntersectionObserverdecide it — the top sentinel condenses the panel once it has scrolledcondenseAfterpast the pinning line, and the panel expands when the top edge offooterenters the viewport (including theexpandMarginlead). That means it works inside the window as well as inside any nested scroll container. - Controlled and uncontrolled: providing
valuemakes it controlled; otherwise it maintains internal state fromdefaultValue(or the first available option in each group).
Accessibility
- Variant groups are
role="radiogroup"and options arerole="radio"witharia-checked; a roving tabindex supports moving the selection within a group with the arrow keys, anddisabledoptions are skipped automatically. - The CTA is a native
<button>with a visible focus ring on focus. - The buy panel is a
role="region"with anaria-label. - When the user has "reduce motion" enabled, the panel stays permanently in its full state with the condense and FLIP animations disabled, and every buy control remains usable throughout.
Bundle Builder
A bundle-building machine — click a product thumbnail and it FLIPs into a slot, the total rolls to its new value, and filling the required number of slots completes the discount ring and unlocks the discount.
Product Drop Countdown
A limited-release drop board with a built-in flip countdown, a CTA that unlocks at launch, and a three-state machine ending in a restock notification form once it sells out.