Skip to content

Components / Sections

Sticky Features

Steps on one side and one visual on the other that stays in view and changes with them, under a lead paragraph that brightens word by word as it is read. The current step is at full strength and the others dim. Zero JavaScript: StickyReveal binds each visual to its step's view timeline and SplitText scrub binds each word of the lead.

Live demo

scroll inside

Sample steps

A post from draft to published

An imaginary publishing tool in four steps. Scroll inside the box: this paragraph brightens as you read it, and the picture on the right changes with each step.

Draft

Write in blocks: a heading, a paragraph, an image.

Review

Each reviewer ticks off the blocks they read.

Schedule

Pick a day on the calendar and a time.

Publish

Readers arrive and the chart counts them per day.

Live, from the library itself, at full size: scroll, hover, navigate away and back. The demo is never gated.

Variants · each row writes the attribute the prop writes; the section's own CSS answers

tone
density
align
media

Measured

JavaScript of its own

0 B

Uses Reveal and the shared runtime (1.9 kB raw, once per site). With those included: 3.0 kB raw.

CSS 17.9 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.

Browser support

Limited availability

Chrome 116 · not Firefox · Safari 26; needs scroll-timeline, timeline-scope. Elsewhere: one column, each step followed by its own visual, and the lead whole (Firefox, and every browser below 40 rem of the section's width).

Install Pro

The demo above is never gated; the source is. With a key in components.json (see account), the registry serves it like any other item:

terminal
npx shadcn@latest add @moonarc-pro/feature-sticky
src/pages/index.astro
---
import FeatureSticky from '@/components/moonarc/pro/FeatureSticky.astro';
---
<!-- top: the sticky bar's height and a gap. Set --ma-feature-sticky-top once in your global CSS instead, and every
     instance reads it -->
<FeatureSticky
  eyebrow="How it works"
  headline="From a store to a report in three steps"
  lead="Connect a store, set your rules, and read the week in one place. Each step below shows the screen it happens on."
  steps={[
    { title: 'Connect a store', body: 'Orders and stock arrive within a minute of the first sync.' },
    { title: 'Set your rules', body: 'Low stock, late orders and returns each raise their own alert.' },
    { title: 'Read the week', body: 'One report every Monday, with the numbers that moved.' },
  ]}
>
  <img slot="visual-1" src="/features/connect.png" alt="" />
  <img slot="visual-2" src="/features/rules.png" alt="" />
  <img slot="visual-3" src="/features/report.png" alt="" />
</FeatureSticky>

Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: sticky-reveal, split-text.

Props

PropTypeDefaultDescription
steps{ title, body? }[]noneThe steps, up to eight: the title is an h3 under the section's h2, the body plain text or HTML. Put each step's visual in slot="visual-{n}", n from 1.
headlinestringnoneThe section's h2. The headline slot wins over it.
eyebrowstringnoneSmall label above the headline. The eyebrow slot wins over it.
leadstringnoneThe paragraph under the headline; its words brighten from dim to full as it moves up the screen. The copy slot wins over it and does not brighten.
media'right' | 'left''right'Side of the sticky visual: the inline end (right) or the inline start (left). On a right-to-left page right is on the left.
topstring'var(--ma-feature-sticky-top, 5rem)'Sticky offset of the visual: your sticky bar's height plus a gap. Set --ma-feature-sticky-top on the page to change every instance.
heightstring'min(70vh, calc(100svh - top - 1.5rem))'Height of each step and of the sticky visual. The default never runs past the bottom of the screen.
tone'surface' | 'muted' | 'ink' | 'brand'unset (paints nothing)The ground: the page surface, a muted band, an always-dark ink panel, or a deep brand panel. Each paints --background, --ma-tone-muted (over --muted, with a --border hairline), --ma-tone-ink or --ma-tone-brand (over --primary) with the matching text. With theme.css, a painted tone also re-declares --card, --border, --input and --ring inside the section, so the visual cards follow the ground.
density'compact' | 'cozy' | 'roomy''cozy'Block padding and the gap under the head ×0.8 / ×1 / ×1.25 through --ma-density.
align'start' | 'center''start'Alignment of the eyebrow, the headline and the lead.

Theming and overrides

The section reads the theme contract (shadcn's variable names with the base tokens as fallback), so a pasted theme or one --ma-brand recolours it. Its class names are the override surface and stay stable; a named slot replaces the matching prop with your own markup.

Classes

.ma-feature-sticky.ma-feature-sticky__aside.ma-feature-sticky__eyebrow.ma-feature-sticky__head.ma-feature-sticky__headline.ma-feature-sticky__inner.ma-feature-sticky__lead.ma-sticky.ma-sticky__body.ma-sticky__step.ma-sticky__steps.ma-sticky__title.ma-sticky__visual.ma-sticky__visuals

Slots

slot="eyebrow"slot="headline"slot="copy"slot="aside"

Reduced motion

The visuals hand over in place without travel, every step stays at full strength, and the lead is whole.

With ClientRouter

Inherits StickyReveal and SplitText; nothing to wire, both are CSS only.

Why it is built this way

Replaces: StickyScroll reveal (Aceternity) · Feature section with sticky screenshot (Tailwind Plus). See the migration table.

Splits a string into words, characters or lines on the server and reveals them with a capped stagger when they scroll into view.

every browserscroll

Steps on the left scroll by; one visual on the right stays put and changes with them.

Chrome 116 · not Firefox · Safari 26scroll

Shows features in alternating rows of copy and a visual.

Chrome 115 · not Firefox · Safari 26scroll

Sample features

Three screens of a sample product

Orders per day

Two weeks of sample orders, one bar a day.

A bar chart of sample orders per day over two weeks

Stock by warehouse

The share of sample stock each warehouse holds.

A ring chart: North 50 %, East 32 %, South 18 % of the sample stock

The latest orders

Order number, customer, items, status and total.

A table of three sample orders with their status and total

A product's features as tabs: a named radio group over one panel per feature, each with its title, body and image, the image optionally in a browser window.

every browserclick

Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown