Page Sections

Feature Section Set

One set of feature data, three finished layouts — an asymmetric bento grid, a sticky side-by-side scroll, and a row of equal-width cards — applied by switching the variant.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/feature-section-set.en.json?t=<install token>"

New project or installation trouble? Start here →

Playground

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

3
0.08
Active edited usageDefault props
<FeatureSection/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add "https://webberui.com/r/feature-section-set.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/feature-section-set.

Usage

All three layouts share the same features array — switch variant to switch styles:

import { Zap, Layers, Sparkles } from "lucide-react";
import { FeatureSection, type Feature } from "@/components/ui/feature-section-set";

const features: Feature[] = [
  { title: "Ready out of the box", description: "One command and it lands.", icon: <Zap /> },
  { title: "Semantic tokens", description: "One consistent voice across the site.", icon: <Layers /> },
  { title: "Animation first", description: "Transitions with care.", icon: <Sparkles /> },
];

<FeatureSection
  variant="bento"
  eyebrow="Why us"
  title="Built for animation"
  features={features}
/>

The three layouts

  • bento: an asymmetric grid. Use colSpan / rowSpan (1–4) to make some features span multiple cells horizontally or vertically; the cards reveal one at a time with a stagger and lift slightly on hover.
  • scroll: a sticky side-by-side scroll (Pro). The visual panel on the left is sticky while the features on the right highlight section by section as you scroll; the panel cross-fades between the corresponding media according to progress and accumulates a progress indicator. Each feature can be clicked to scroll back to its position.
  • cards: a row of equal-width cards. The simplest single row of feature cards, with the same staggered reveal and hover lift.

Side-by-side scrolling and the scroll container

The scroll layout derives the active feature index using useScroll. If the scrolling happens inside a nested overflow-y: auto container (like the example above), pass that container's container ref; by default the window is the scroll container:

const scrollerRef = React.useRef<HTMLDivElement>(null);

<div ref={scrollerRef} className="h-[400px] overflow-y-auto">
  <FeatureSection
    variant="scroll"
    features={features}   // each feature carries media for the panel to show
    container={scrollerRef}
    stickyTop={16}
  />
</div>

The layout's column count is decided by a container query (@container), taking the width from the component itself rather than the window, so it falls back to a single stacked column automatically inside narrow containers or on phones.

Props

FeatureSection

PropTypeDefaultDescription
featuresFeature[]—The feature data array, shared by all three styles
variant"bento" | "scroll" | "cards""bento"Layout style
eyebrowReact.ReactNode—The kicker above the title
titleReact.ReactNode—The section's main heading
descriptionReact.ReactNode—The section's description
columnsnumber3Column count for bento / cards in wide layouts (1–4)
stickyTopnumber96Distance from the sticky scroll panel to the top of the container (px)
containerReact.RefObject<HTMLElement | null>—Ref of the nested scroll container for scroll; by default the window is the scroll container
staggernumber0.08Per-item interval for the cards revealing (seconds)
oncebooleantruePlay the reveal animation only the first time the element enters the viewport
classNamestring—Class appended to the outermost <section>

Feature

PropTypeDefaultDescription
titleReact.ReactNode—Feature title
descriptionReact.ReactNode—Feature description text
iconReact.ReactNode—Icon before the title (scaled to 20px automatically)
mediaReact.ReactNode—The visual content the sticky panel shows while this feature is active in the scroll style
colSpannumber1Horizontal column span for bento (1–4, only takes effect in wide layouts)
rowSpannumber1Vertical row span for bento (1–4, only takes effect in wide layouts)
idstring—Anchor id, applied to that feature's outer container
classNamestring—Class appended to that feature's card/block

How it works

  • All three layouts share the same data, so switching variant switches the layout — you never rewrite the feature copy for a different section
  • scroll follows the same useScroll(container / target) convention as card-stack-scroll and derives the active index with useMotionValueEvent, triggering a repaint only when the index changes
  • The sticky panel is done with position: sticky (driven by layout rather than scroll events), and the corresponding media cross-fades through AnimatePresence
  • Column counts and cell spans both go through container queries (@container plus @lg: variants), so responsiveness is measured against the component's width rather than the window's

Accessibility

  • When the user has "reduce motion" enabled at the system level, the reveal, the hover, the cross-fade, and smooth scrolling are all disabled, while the scroll layout's sticky panel and its active pairing are preserved (they are layout, not motion)
  • In scroll, each feature is a native <button>: it can be focused with the keyboard and triggered with Enter to scroll back to its position, and the active item is marked with aria-current
  • Decorative icons and the sticky visual panel are marked aria-hidden, and headings are presented as semantic <h2> / <h3>