Skip to content

Components / Sections

Screens Scroll

An app's features told down the page while one phone stays in view beside them and its screen changes with each feature. Every feature names its own view timeline; each screen fades in on its feature's timeline over the one before it, so between two features the phone is never empty and scrolling back turns the screens back. A line beside each feature fills as it is read. Where view timelines are missing, below 64 rem, or with reduced motion, each feature shows its own phone under its text. The screens are drawn from rows (AppScreen). Zero JavaScript.

Live demo

scroll inside

How it works

One tap a day

Four screens of an imaginary habit tracker. Scroll inside the box: the phone on the right follows the feature you read.

  1. Today

    Every habit on one list

    Tick each one off as it is done. The ring fills as the day does.

  2. Streaks

    Days in a row, kept for you

    A week you keep turns solid, and a missed day does not wipe the month.

    • One rest day a week
    • A nudge at your usual time
  3. Stats

    See what stuck

    Each week as bars, the best day lit.

  4. Friends

    Keep each other going

    Share a streak with a friend and cheer theirs.

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 29.5 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: each feature shows its own phone under its text (Firefox, and Safari before 26).

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/screens-scroll
src/pages/index.astro
---
import ScreensScroll from '@/components/moonarc/pro/ScreensScroll.astro';
---
<ScreensScroll
  eyebrow="How it works"
  headline="One tap a day"
  features={[
    { eyebrow: 'Today', title: 'Your habits, one list', text: 'Tick each one off as it is done.', screen: { title: 'Today', label: 'The today list', rows: [{ kind: 'checks', items: [{ text: 'Walk', done: true }, { text: 'Read' }] }] } },
    { eyebrow: 'Streaks', title: 'Days in a row', text: 'A week you keep turns solid.', screen: { title: 'Streak', label: 'A six-day streak', rows: [{ kind: 'days', days: 'xxxxxx.' }] } },
    { eyebrow: 'Insights', title: 'What sticks', text: 'Your best days, by the hour.', screen: { title: 'Insights', label: 'A week of completed habits', rows: [{ kind: 'bars', values: [3, 4, 2, 4, 4, 3, 1] }] } },
  ]}
/>

Without a key the registry answers 401 with an upgrade message. Everything this section composes is free: phone-frame, app-screen, reveal.

Props

PropTypeDefaultDescription
features{ eyebrow?; title; text; points?; screen }[]noneTwo to six features, top to bottom. screen is AppScreen's props; give it a label, which is what a screen reader hears after the feature.
headlinestringnoneThe section's heading. Slot "headline" wins and may hold an <em>.
eyebrowstringnoneA short line above the heading.
copystringnoneA line under the heading.
block1 | 2 | 3 | 4 | 51The colour block of the disc behind the held phone.
model'island' | 'notch' | 'punch''island'The phone's cut-out.
level2 | 32Heading level of the heading; each feature's title is one below.
media'right' | 'left''right'Side of the held phone.
viewstring'100vh'The height of what scrolls the section: the viewport, unless it scrolls inside a box (pass 100cqh and make the box a size container), so the features and the held phone are sized by what is seen.
tone'surface' | 'muted' | 'ink' | 'brand'unset (paints nothing)The section's ground.
density'compact' | 'cozy' | 'roomy''cozy'Block padding ×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-scrs.ma-scrs__body.ma-scrs__copy.ma-scrs__desc.ma-scrs__disc.ma-scrs__eyebrow.ma-scrs__feature.ma-scrs__features.ma-scrs__head.ma-scrs__headline.ma-scrs__inner.ma-scrs__kicker.ma-scrs__own.ma-scrs__phone.ma-scrs__points.ma-scrs__screen.ma-scrs__screens.ma-scrs__stage.ma-scrs__sticky.ma-scrs__text.ma-scrs__title

Slots

slot="headline"

Reduced motion

Each feature shows its own phone under its text; nothing is bound to the scroll.

With ClientRouter

CSS only.

Why it is built this way

Replaces: Sticky phone feature sections built with GSAP ScrollTrigger · Scrollytelling app tours. See the migration table.

A phone app's screen drawn from rows instead of a screenshot: a status bar, a title, then rows of fourteen kinds (a big figure, a ring of progress, a checklist, a bar chart, a card, chips, a map with a route, a player, a chat, a list, a week of days, a button, a lock screen's clock, a notification) and a tab bar.

every browserload

A generic phone frame around a screenshot or any content, with three cut-outs (island, notch, punch), a home bar, and no logo, buttons or brand.

every browseralways

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

Chrome 116 · not Firefox · Safari 26scroll

A hero for a mobile app: the headline, the rating line (stars, the average, how many rated and where) and the store buttons beside a phone on a disc of the app's colour.

every browserload

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.

Chrome 116 · not Firefox · Safari 26scroll

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