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.
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.
Today
Every habit on one list
Tick each one off as it is done. The ring fills as the day does.
9:41Friday 9 OctoberToday3 of 4habits done- Walk 20 minutes7:40
- Read 10 pages
- No phone after 10
- Water the plants
TodayStatsFriendsStreaks
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
9:41Walk 20 minutesStreakDays in a row12Your longest yetThis weekLast weekTodayStatsFriendsStats
See what stuck
Each week as bars, the best day lit.
9:41This weekStatsDone this week234 more than last weekWeekMonthYearTodayStatsFriendsFriends
Keep each other going
Share a streak with a friend and cheer theirs.
9:41Friends- IInesRunning31
- KKofiReading18
- MMeiPiano9
Day 31!Catching you by springTodayStatsFriends
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
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
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:
npx shadcn@latest add @moonarc-pro/screens-scroll---
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
| Prop | Type | Default | Description |
|---|---|---|---|
features | { eyebrow?; title; text; points?; screen }[] | none | Two 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. |
headline | string | none | The section's heading. Slot "headline" wins and may hold an <em>. |
eyebrow | string | none | A short line above the heading. |
copy | string | none | A line under the heading. |
block | 1 | 2 | 3 | 4 | 5 | 1 | The colour block of the disc behind the held phone. |
model | 'island' | 'notch' | 'punch' | 'island' | The phone's cut-out. |
level | 2 | 3 | 2 | Heading level of the heading; each feature's title is one below. |
media | 'right' | 'left' | 'right' | Side of the held phone. |
view | string | '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
- The screens are stacked in one cell and each only ever fades in, over the one before, on its feature's timeline: the phone shows the latest feature reached, and there is no gap between two features in which no screen is lit.
- The timelines are named per instance (timeline-scope on the section, each feature's name written into its style), so two of these on one page never read each other's scroll.
- The held phone is a picture of the same screens and is hidden from a screen reader; each feature keeps its own phone in the page for it, out of sight, so the screen is heard right after the feature it belongs to.
- The held phone is as tall as the viewport allows (its width is worked out from the viewport's height), so it never runs under the browser's edge.
Replaces: Sticky phone feature sections built with GSAP ScrollTrigger · Scrollytelling app tours. See the migration table.
Related
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.
Sticky Reveal
ScrollSteps on the left scroll by; one visual on the right stays put and changes with them.
App Hero
ProA 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.
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.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown