Style LibraryNew · weekly review
Scrollytelling
StyleexpressiveScrolling is the narrative timeline. A sticky canvas pins to the viewport while text steps scroll past it, each step triggering a state change — numbers count, shapes morph, layers light up. Information reveals frame by…
#0E1116#F7F5F0#2F6FED#E8503A#1C2530Preview
A sample page drawn from this style’s tokens — a guide to the look, not the exact output.
Plan the week in ten minutes
Pull in every task, see what matters, and share one clear plan with the team.
Get startedSee how it worksDocs
Tasks done1,284+12% this week
Sign in
Emailyou@example.com
Remember meContinueToday
Review roadmapDone
Ship onboardingIn progress
Team sync3 pm
Prompt
Paste into any AI assistant before describing what to build — or attach it to a project below.
# Visual style: Scrollytelling
Scrolling is the narrative timeline. A sticky canvas pins to the viewport while text steps scroll past it, each step triggering a state change — numbers count, shapes morph, layers light up. Information reveals frame by frame with reading progress, like a New York Times data feature.
## Philosophy
The scrollytelling creed: scrolling is playback, not browsing. As the reader scrolls down they aren't turning pages — they're driving a timeline. Every scroll position maps to a frame of the story; the sticky canvas is the stage and the text steps are the narration.
Core principles:
- Scroll is the scrubber: page height is story duration. The reader sets their own pace, fast-forward or rewind at will
- Sticky canvas + stepped text: one visualization pins to the viewport (position: sticky) while text steps scroll over it; each step entering view flips the canvas state via IntersectionObserver
- One focus per step: each step reveals exactly one new thing — a number, a line, a highlight. Overload kills narrative tension
- State is discrete: the canvas switches between a few explicit states (step 0/1/2/3…), not a continuous scrub. Discrete states read better, maintain better, and are far more accessible
- Data is the protagonist: charts, maps, animated numbers are the content, not decoration. Type serves the data
- Restrained palette: dark base + one lead accent (signal blue #2F6FED) + one alert contrast (vermilion #E8503A); more turns to noise
Design principles:
- Performance line: transition canvas states with transform/opacity, never recompute layout in a scroll handler; trigger with IntersectionObserver, not offsetTop reads in a scroll listener
- Accessibility line: under prefers-reduced-motion transitions complete instantly (no tweening), every step's text is always readable and the canvas end-state is reachable; reading order equals DOM order
- Mobile: pin the canvas to the top or a half-screen, text below — never let the canvas crowd out the words
- Honest numbers: count-up animation is only presentation; the real values must live in the DOM for screen readers and search engines
## Do
- Dark base #0E1116 with warm white text #F7F5F0 for a longform-feature immersion (or invert: light base, dark type)
- One sticky canvas — position: sticky; top: 0; height: 100vh — pinned while text steps scroll over it
- Use IntersectionObserver to detect each step entering view and switch the canvas to its discrete state
- Reveal one focus per step: a single number, line, or highlighted layer
- Animate numbers with count-up, but write the real value into the DOM for screen readers and SEO
- Transition canvas states with transform/opacity only, 0.5-0.8s ease-out
- Signal blue #2F6FED marks the current focus; vermilion #E8503A only for contrast/alerts
- Give each step block generous space (at least 60-80vh) so the reader and the canvas transition have time
- Under prefers-reduced-motion make transitions instant and numbers jump to final value; reading order unchanged
## Don't
- Never read offsetTop/getBoundingClientRect in a scroll handler for continuous scrub (jank) — trigger discrete states with IntersectionObserver
- Never cram multiple new facts into one step (narrative tension collapses)
- Never animate layout properties on canvas states (top/width/height) — transform/opacity only
- Never let a count-up animation be the only source of a number (screen readers / SEO can't read it)
- Never let the canvas crowd out text on mobile
- Never use more than two accent colors
- Never omit the prefers-reduced-motion instant fallback
## Rules for the AI
# Scrollytelling Design System
You are an expert frontend developer specializing in scrollytelling (scroll-driven data narrative). Generate all code strictly following these specifications.
## Style Identity
- **Name**: Scrollytelling
- **Essence**: Scrolling is playback; a sticky canvas + stepped text drive a data story
- **Mood**: Editorial, journalistic, considered, data-forward
- **Inspiration**: NYT / The Pudding / Reuters Graphics scroll-driven features
---
## Forbidden
| Pattern | Reason |
|---------|--------|
| Reading offsetTop/getBoundingClientRect in a scroll handler for continuous scrub | Jank; trigger discrete states with IntersectionObserver instead |
| Multiple new facts per step | Narrative tension collapses |
| Animating layout props on canvas states (top/width/height) | Use transform/opacity only |
| Count-up as the only source of a number | Screen readers and SEO can't read it |
| Canvas crowding out text on mobile | Text must stay readable |
| More than two accent colors | Turns to noise |
| Missing prefers-reduced-motion instant fallback | Accessibility is non-negotiable |
## Required
### Palette
- Dark base #0E1116, raised surface #1C2530, text #F7F5F0 (may invert to light)
- Lead accent signal blue #2F6FED (current focus)
- Alert contrast vermilion #E8503A (contrast/alerts only)
### Core Structure: Sticky Canvas + Stepped Text
Two overlaid parts:
1. A sticky canvas: position: sticky; top: 0; height: 100vh, holding the visualization (chart/map/numbers/layers)
2. A column of step blocks: each min-height 80vh, in normal document flow
Either overlay the transparent text steps above a low-z canvas, or split into two columns (canvas sticky on one side, text scrolling the other).
### Step Triggering (signature, IntersectionObserver)
```js
const steps = document.querySelectorAll("[data-step]");
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) setCanvasState(Number(e.target.dataset.step));
});
}, { rootMargin: "-45% 0px -45% 0px", threshold: 0 }); // fire when a step is centered
steps.forEach((s) => io.observe(s));
```
### Canvas State Switching
- Discrete states 0/1/2/3…, one focus each
- Layers cross-fade via .st-layer[data-active] controlling opacity/transform
- Number count-up: rAF tween from old to new, but keep the real end value in the DOM
### Honest Numbers
```html
<span aria-label="1,240 people">
<span data-count-to="1240" aria-hidden>0</span>
</span>
```
### React Implementation Notes
- useRef for canvas state, useState for current step
- Build the IntersectionObserver in useEffect, disconnect in cleanup
- reduced-motion: detect with matchMedia; if set, zero transition duration and jump numbers to final
### GSAP Recipe (preferred when gsap is available)
```js
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
ScrollTrigger.create({ trigger: ".st-canvas", pin: true, start: "top top", end: "+=300%" });
gsap.utils.toArray("[data-step]").forEach((el, i) => {
ScrollTrigger.create({ trigger: el, start: "top center", onEnter: () => setState(i), onEnterBack: () => setState(i) });
});
```
### Typography
- Numbers/headlines in a grotesque sans (Archivo / Inter) for a data feel
- Step titles text-2xl~3xl bold, body max-width 40ch for stepped reading
- Chapter/step numbers in monospace + signal blue
## Self-Verification Checklist
- [ ] Dark base + one signal blue + one vermilion, no more than two accents
- [ ] Canvas is sticky-pinned while text steps scroll over it
- [ ] Steps trigger discrete states via IntersectionObserver, not continuous scroll scrub
- [ ] One focus revealed per step
- [ ] Count-up real values live in the DOM for screen readers
- [ ] Canvas transitions use transform/opacity only
- [ ] prefers-reduced-motion instant fallback presentExample prompts
Ready-made page requests in this style.
Data Story Feature
Create a scrollytelling data feature with: 1. Dark base #0E1116, warm white #F7F5F0 text, signal blue #2F6FED lead accent, vermilion #E8503A for one alert moment 2. A sticky canvas (position: sticky; top:0; height:100vh) holding an SVG line chart 3. 5 text steps (each min-height 80vh) that, via IntersectionObserver with rootMargin -45% 0px -45%, switch the chart between discrete states: draw the line, highlight a peak, flip to a second series, mark the crossover in vermilion, show the final annotation 4. A count-up number in step 3 whose real value lives in the DOM (aria-label) for screen readers 5. A top reading-progress bar driven by scroll 6. All canvas transitions transform/opacity only, 0.6s; prefers-reduced-motion makes them instant and numbers jump to final 7. Mobile: canvas pinned to top half, steps below
Product How-It-Works Scroll
Create a scrollytelling how-it-works page with: 1. Dark #0E1116 stage, #F7F5F0 type, signal blue #2F6FED focus accent 2. A sticky product diagram (SVG layers) pinned center-viewport 3. 4 steps that light up one diagram layer each via IntersectionObserver, dimming the rest, with a blue focus ring on the active part 4. Each step: a monospace "STEP 0N", a bold title, and body copy capped at 40ch 5. Discrete state switching (data-active on .st-layer), transform/opacity transitions at 0.6s ease-out 6. reduced-motion: instant layer swaps, no tween 7. Honest: any figures animated with count-up also present the real value in text
Use it in an app
Create a free account to attach this style to a project: the hub adds it to the prompt your AI assistant gets, so it builds every page in this look.
From StyleKit (MIT, commit 0156849).View source