WebberUI

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.

How to install Pro components →See the plans →

Loading preview…
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.

3
0.08
<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. 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.ReactNodeThe kicker above the title
titleReact.ReactNodeThe section's main heading
descriptionReact.ReactNodeThe 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
classNamestringClass appended to the outermost <section>

Feature

PropTypeDefaultDescription
titleReact.ReactNodeFeature title
descriptionReact.ReactNodeFeature description text
iconReact.ReactNodeIcon before the title (scaled to 20px automatically)
mediaReact.ReactNodeThe 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)
idstringAnchor id, applied to that feature's outer container
classNamestringClass 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>

On this page