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
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/feature-section-set.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.
<FeatureSectionSet />
Installation
npx shadcn@latest add "https://webberui.com/r/feature-section-set.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. UsecolSpan/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 correspondingmediaaccording 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
| Prop | Type | Default | Description |
|---|---|---|---|
features | Feature[] | — | The feature data array, shared by all three styles |
variant | "bento" | "scroll" | "cards" | "bento" | Layout style |
eyebrow | React.ReactNode | — | The kicker above the title |
title | React.ReactNode | — | The section's main heading |
description | React.ReactNode | — | The section's description |
columns | number | 3 | Column count for bento / cards in wide layouts (1–4) |
stickyTop | number | 96 | Distance from the sticky scroll panel to the top of the container (px) |
container | React.RefObject<HTMLElement | null> | — | Ref of the nested scroll container for scroll; by default the window is the scroll container |
stagger | number | 0.08 | Per-item interval for the cards revealing (seconds) |
once | boolean | true | Play the reveal animation only the first time the element enters the viewport |
className | string | — | Class appended to the outermost <section> |
Feature
| Prop | Type | Default | Description |
|---|---|---|---|
title | React.ReactNode | — | Feature title |
description | React.ReactNode | — | Feature description text |
icon | React.ReactNode | — | Icon before the title (scaled to 20px automatically) |
media | React.ReactNode | — | The visual content the sticky panel shows while this feature is active in the scroll style |
colSpan | number | 1 | Horizontal column span for bento (1–4, only takes effect in wide layouts) |
rowSpan | number | 1 | Vertical row span for bento (1–4, only takes effect in wide layouts) |
id | string | — | Anchor id, applied to that feature's outer container |
className | string | — | Class appended to that feature's card/block |
How it works
- All three layouts share the same data, so switching
variantswitches the layout — you never rewrite the feature copy for a different section scrollfollows the sameuseScroll(container / target)convention ascard-stack-scrolland derives the active index withuseMotionValueEvent, 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 correspondingmediacross-fades throughAnimatePresence - Column counts and cell spans both go through container queries (
@containerplus@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
scrolllayout'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 witharia-current - Decorative icons and the sticky visual panel are marked
aria-hidden, and headings are presented as semantic<h2>/<h3>
Hero Collection 02
Three advanced hero sections — a fluid gradient canvas, a CSS 3D parallax tilt, and a text-masked light sweep — all variants of a single component.
CTA + Footer Set
A call-to-action section that reveals in sequence as it enters the viewport, paired with a multi-column and a minimal animated footer.