Selected Work Rail

About this block

Selected Work RailPRO

A pinned scroll feature section for a design studio built on the scroll-rail component: the eyebrow, heading, intro, an Arrow Pill and an underline link stay fixed in a header row while a rail of large project cards slides sideways beneath as the page scrolls. Each card is a link with a cover photograph that drifts inside its frame, the client, year, a one-line summary and the disciplines, and it dims gently until it reaches the middle. A closing panel with a project count and an Arrow Pill ends the rail, and a thin hairline under the rail shows how far along it is. Every rail setting passes through the rail prop. Reduced motion turns the rail into a plain horizontal scroll.

Feature314: Selected Work Rail

A pinned scroll section where a header row (eyebrow, heading, intro and two buttons) stays fixed while a rail of project cards slides sideways beneath it, each cover photo drifting inside its frame, followed by an optional closing panel and a hairline 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.

Upgrade Now

Installation

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

bash
npx shadcn add "https://ui.beste.co/r/feature314?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/feature314?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/feature314.tsx plus the scroll-rail component and the button23 and button2 buttons it uses.

Quick start

The installed file exports feature314Demo alongside the block: the exact props behind the preview above. Spread it to get a working section in one line.

tsx
import { Feature314, feature314Demo } from "@/components/beste/block/feature314";

export default function Page() {
  return <Feature314 {...feature314Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Feature314 } from "@/components/beste/block/feature314";

export default function Page() {
  return (
    <Feature314
      eyebrow="Selected work 2019 to 2026"
      heading="Made to be looked at twice."
      description="A design studio working on identity, packaging and spaces."
      buttons={[
        { label: "Start a project", href: "/contact" },
        { label: "Meet the studio", href: "/about" },
      ]}
      projects={[
        {
          client: "Halden Optics",
          summary: "A camera body and the whole world around it",
          disciplines: "Product, identity",
          year: "2025",
          image: "https://images.unsplash.com/photo-1526170375885-4d8ecf77b99f?w=1400&q=80",
          imageAlt: "A camera photographed on a plain backdrop",
          href: "/work/halden",
          shape: "landscape",
        },
        {
          client: "Oriel Botanicals",
          summary: "Packaging for a small line of plant oils",
          disciplines: "Packaging, art direction",
          year: "2024",
          image: "https://images.unsplash.com/photo-1617897903246-719242758050?w=1400&q=80",
          imageAlt: "A dropper bottle of golden oil on a round wooden board",
          href: "/work/oriel",
          shape: "wide",
        },
      ]}
      more={{ heading: "48 projects since 2014", label: "See all work", href: "/work" }}
      rail={{ gap: 40, align: "stretch", smoothing: 0.5 }}
    />
  );
}

Props

PropTypeDefaultDescription
eyebrowstring–Small line above the heading
headingstring–Section heading, rendered as an h2
descriptionstring–Intro paragraph in the right column of the header
buttonsActionButton[][]The first renders as a dark Button23 arrow pill, the rest as Button2 underline links
projectsProject[][]Cards along the rail, each a link to its href
moreMoreCard–Closing panel after the last project
railOmit<ScrollRailProps, "children" | "progress" | "autoplay" | "className">–Forwards the ScrollRail component's props (gap, align, speed, smoothing, paused)
classNamestring–Extra classes for the outer <section>
ts
type ActionButton = {
  label: string;
  href: string;
};

type Project = {
  client: string;
  summary: string;
  disciplines: string;
  year: string;
  image: string;
  imageAlt: string;
  href: string;
  shape?: "landscape" | "wide"; // 4 by 3 or 16 by 10, default "landscape"
};

type MoreCard = {
  heading: string;
  description?: string;
  label: string;
  href: string;
};

Behavior notes

  • The section is h-[320svh] tall with a sticky inner frame, and rail travel maps scroll progress from 0.03 to 0.95 onto 0 to 1, so the rail rests briefly at both ends.
  • Card height comes from the rail through container query units, and the cover width is that height times 1.3333 (landscape) or 1.6 (wide), capped at 82vw.
  • Each cover image is 116% wide with data-rail-depth="0.14", so it drifts against the travel, and the caption block uses data-rail-focus="0.5" to dim until its card reaches the middle. Hovering a card scales the cover to 1.03.
  • The first 3 covers load eagerly and the rest lazily.
  • The same scroll value drives the hairline under the rail through scaleX, so both always agree.
  • With reduced motion, the section loses its extra height, the rail gets progress={0} and becomes a plain horizontal scroll, the header skips its rise-in and the hairline is not rendered.
  • autoplay is always off here; the rail only moves with the page.

More Feature blocks

View all Feature
PRO

feature315

Services Slide Rail

PRO

feature318

Collection Rail

PRO

feature316

Figures Rail

Get Pro access