Skip to content

Components / Sections

Feature Rows

Shows features in alternating rows of copy and a visual. The copy reveals from its side, and the visual rises and sharpens exactly in step with the scroll. One slot per row for a live demo, a screenshot or a video. Zero JavaScript of its own.

Live demo

scroll inside

01 · tools that run on your code

Audits that run on your code

audit_motion checks your components and styles against 13 rules, among them transition: all, transitions and keyframes on layout properties, motion with no prefers-reduced-motion branch, hover motion that fires on touch, and astro:page-load listeners with no teardown.

The CI recipe →
pull request comment
$ npx -y @moonarc/mcp audit src
moonarc audit · 1 files · 2 errors, 1 warnings, 1 notes

  error src/components/Hero.astro:7  [transition-all] `transition: all` animates every property, including layout
  error src/components/Hero.astro:11  [page-load-listener] `astro:page-load` listener without an `astro:before-swap` teardown
  warn  src/components/Hero.astro:1  [no-reduced-motion] file animates but has no reduced-motion branch
  info  src/components/Hero.astro:1  [hover-not-gated] :hover motion not gated on (hover: hover)

02 · a cadence you can audit

New components every month

New components every month, each listed with its date in a public changelog, so you can check the cadence before you pay.

The changelog →

changelog · public, dated

2026-09-28fix: what the audit of every product found

2026-09-28the logo

2026-09-26fix: what the component audit found

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
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 11.6 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 115 · not Firefox · Safari 26; needs scroll-timeline. Elsewhere: visuals sit in place; the copy still reveals (Firefox).

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-rows
src/pages/index.astro
---
import FeatureRows from '@/components/moonarc/pro/FeatureRows.astro';
---
<FeatureRows rows={[
  { kicker: '01', title: 'Survives navigation', body: 'Every scripted component rebinds after each swap.', href: { href: '/docs/view-transitions/', label: 'How' } },
  { kicker: '02', title: 'Measured in a real build', body: 'A fixture build per component.' },
]}>
  <img slot="visual-0" src="/nav.png" alt="" />
  <img slot="visual-1" src="/budget.png" alt="" />
</FeatureRows>

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

Props

PropTypeDefaultDescription
rows{ kicker?, title, body, href? }[]noneRows, alternating sides. Put the visual in slot="visual-{index}".
media'right' | 'left' | 'none'alternatingSide of the visuals: alternating by default, or every visual right or left; none drops them.
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 cards and fields follow the ground. Decoration is strongest on brand.
density'compact' | 'cozy' | 'roomy''cozy'Block padding and the gap between rows ×0.8 / ×1 / ×1.25 through --ma-density.

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-rows.ma-feature-rows__body.ma-feature-rows__copy.ma-feature-rows__eyebrow.ma-feature-rows__head.ma-feature-rows__headline.ma-feature-rows__kicker.ma-feature-rows__lead.ma-feature-rows__link.ma-feature-rows__row.ma-feature-rows__title.ma-feature-rows__visual

Slots

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

Reduced motion

Copy fades in place; visuals are static.

With ClientRouter

Inherits Reveal and ScrollScrub; nothing to wire.

Why it is built this way

Replaces: Feature sections with screenshots (Tailwind Plus) · StickyScroll reveal (Aceternity). See the migration table.

Entrance on scroll that plays once and stays, staggers its children, scrubs with the scroll position when asked, honours reduced motion, and keeps working after every ClientRouter navigation.

every browserscroll

Bind translate, scale, rotate and opacity to the scroll position: the element goes from the start values you give to neutral across a range of its own passage through the viewport, exactly in step.

Chrome 115 · not Firefox · Safari 26scroll

Lays out features as a bento grid: one slab split by 1 px hairlines, with the page ground showing through the gaps.

every browserscroll

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