$240
Solid ash with a soap finish
Fenwick & Ash, the spring collection
$240
Solid ash with a soap finish
$180
Molded seat on oak legs
$28
Glazed stoneware, 350 ml
$2,900
Aniline leather, three seats
$190
Powder coated steel
$64
Hand thrown earthenware
A pinned scroll feature section for a furniture and homeware studio built on the scroll-rail component: an eyebrow and heading sit in a fixed header with an underline link to the shop, while a new collection travels sideways beneath it as the page scrolls. Each product rests on its own softly tinted plate like a print on a mat and lifts slightly on hover, with its name, price, a one-line material note and a small add to bag button below. Every third panel is a wider room photograph that drifts inside its frame, with a one-line caption. A thin line under the rail fills with the scroll. Reduced motion turns the rail into a plain horizontal scroll.
A pinned scroll section where an eyebrow, heading and shop link stay fixed while a rail of product plates and wider room photographs slides sideways beneath, each product with a name, price, material line and an add to bag button, and a line under the rail that fills with the scroll.
Upgrade to Pro
Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.
Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.
Radix flavor
npx shadcn add "https://ui.beste.co/r/feature318?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/feature318?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/feature318.tsx plus the scroll-rail component and the button2 button it uses.
The installed file exports feature318Demo alongside the block: the exact props behind the preview above. Spread it to get a working section in one line.
import { Feature318, feature318Demo } from "@/components/beste/block/feature318";
export default function Page() {
return <Feature318 {...feature318Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
"use client";
import { Feature318 } from "@/components/beste/block/feature318";
export default function Page() {
return (
<Feature318
eyebrow="The spring collection"
heading="Seven new pieces for slower rooms."
button={{ label: "Shop the collection", href: "/shop" }}
items={[
{
kind: "product",
name: "Loft stool",
material: "Solid ash with a soap finish",
price: "$240",
href: "/shop/loft-stool",
image: {
src: "https://images.unsplash.com/photo-1503602642458-232111445657?w=1200&q=80",
alt: "Pale ash stool against a soft blue wall",
},
tone: "#dfe5ec",
},
{
kind: "moment",
caption: "Set for eight, with room for one more.",
image: {
src: "https://images.unsplash.com/photo-1544457070-4cd773b4d71e?w=1200&q=80",
alt: "Dining room with grey chairs around a pale table",
},
},
]}
addLabel="Add to bag"
addedLabel="Added"
onAdd={(product) => console.log("Added", product.name)}
rail={{ gap: 40, align: "stretch", smoothing: 0.5 }}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
eyebrow | string | – | Small line above the heading |
heading | string | – | Section heading, rendered as an h2 |
button | ActionButton | – | Button2 underline link on the right of the header |
items | (Product | Moment)[] | [] | Products and room photographs, in rail order |
addLabel | string | "Add to bag" | Label on each product's add button |
addedLabel | string | "Added" | Label shown briefly after the button is pressed |
onAdd | (product: Product) => void | – | Called with the product when its add button is pressed |
rail | Omit<ScrollRailProps, "children" | "progress" | "autoplay" | "className"> | – | Forwards the ScrollRail component's props (gap, align, speed, smoothing, paused) |
className | string | – | Extra classes for the outer <section> |
type ActionButton = {
label: string;
href: string;
};
type Product = {
kind: "product";
name: string;
material: string; // one line on material and size
price: string;
href: string;
image: { src: string; alt: string };
tone?: string; // plate color, default "#ebe7e1"
};
type Moment = {
kind: "moment";
caption: string;
image: { src: string; alt: string };
};h-[320svh] tall with a sticky inner frame, and rail travel maps scroll progress from 0.03 to 0.95 onto 0 to 1; the same value fills the line under the rail through scaleX.product panel is a solid plate in its tone color with the photo inset by 8%; on hover the photo lifts 6px and scales to 1.04. The plate and the name both link to href.moment panel is about 1.4 times as wide as its height and its photo is 118% wide with data-rail-depth="0.16", so it drifts against the travel. Captions and product details dim through data-rail-focus until they reach the middle.addedLabel for 1.6 seconds, one product at a time; the timer is cleared on unmount. The block holds no cart state, so wire onAdd to your own.items order; nothing forces a room photo into every third slot.progress={0} and becomes a plain horizontal scroll, and the progress line is not rendered.