HomeStylesTemplates
StyleKit
StylesTemplatesCommunity
  1. Home
  2. /Styles
  3. /Scrollytelling
Style Catalog/Scrollytelling

Scrollytelling

滚动叙事

B
79/100Good
Rate this style

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.

scrollytellingscroll-driven storytellingscroll animationsticky scrolldata journalismnarrative visualizationscroll-triggered reveallong-form featureinteractive storyreading progress barstepper layout
View Full Showcase →
Templates

Best For

scrollytelling / scroll-driven storytelling / scroll animation

Primary Move

Dark base #0E1116 with warm white text #F7F5F0 for a longform-feature immersion (or invert: light base, dark type)

Watch Out

Never read offsetTop/getBoundingClientRect in a scroll handler for continuous scrub (jank) — trigger discrete states with IntersectionObserver

Showcase Entry

Live preview of the showcase page. Click to explore the full experience.

View Full Showcase →

Color Palette

Primary

#0E1116

Secondary

#F7F5F0

Accent 1

#2F6FED

Accent 2

#E8503A

Accent 3

#1C2530

AI ImplementationRatings & FeedbackComponent PreviewReadinessFAQExports

AI Implementation

Copy the Hard Prompt first, then use the spec when needed

Use the Hard Prompt by default to generate UI. Use the Design Spec to understand, modify, and review the style. Use the Creative Brief for early exploration.

Hard Prompt

Use this by default: copy it, append the concrete requirement, and let AI generate consistent production UI.

When to use

  • -When AI should generate UI directly
  • -When repeated outputs must stay consistent
  • -When style drift is the main risk

How to use

  • -Copy the full prompt
  • -Append the concrete requirement
  • -Review against forbidden rules and UI states
STYLEKIT_STYLE_REFERENCE
style_name: Scrollytelling
style_slug: scrollytelling
style_source: /styles/scrollytelling

# Hard Prompt

## When To Use
Use this when you want AI to generate code with strict style consistency. It is the safest default for production UI.

## How To Use
- Copy the full prompt into ChatGPT, Claude, Cursor, or another coding assistant.
- Append the concrete product/page requirement after the prompt.
- After generation, check the forbidden rules and interaction states before accepting the output.

Strictly follow the style rules below and maintain consistency. No style drift allowed.

## Requirements

- Prioritize style consistency first, then creative extension.
- When conflicts arise, treat prohibitions as the highest priority.
- Self-check before output: verify colors, typography, spacing, and interactions still match this style.

## Style Rules

# 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 present

---

# Scrollytelling Design System

> 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.

## Design 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

---

## Token Dictionary (exact class mapping)

### Border
```
Width: border
Color: border-white/10
Radius: rounded-md
```

### Shadow
```
sm: shadow-[0_1px_2px_rgba(0,0,0,0.4)]
md: shadow-[0_4px_16px_rgba(0,0,0,0.5)]
lg: shadow-[0_16px_48px_rgba(0,0,0,0.6)]
hover: hover:shadow-[0_8px_28px_rgba(47,111,237,0.25)]
focus: focus-visible:ring-2 focus-visible:ring-[#2F6FED]/50 focus-visible:ring-offset-2 focus-visible:ring-offset-[#0E1116]
```

### Interaction
```
Hover translate: (none)
Hover scale: (none)
Hover opacity: hover:bg-[#2560d4]
Transition: transition-all duration-200 ease-out
Active: active:scale-[0.98]
```

### Typefaces
```
Heading: font-bold text-[#F7F5F0] tracking-tight
Body: text-[#F7F5F0]/70 leading-relaxed
Mono: font-mono text-[#2F6FED] uppercase tracking-widest
```

### Type scale
```
Hero: text-5xl md:text-7xl
H1: text-4xl md:text-5xl
H2: text-3xl md:text-4xl
H3: text-xl md:text-2xl
Body: text-base md:text-lg
Small: text-sm
```

### Spacing
```
Section: py-20 md:py-28
Container: px-6 md:px-8
Card: py-4 pl-6
Gap sm: gap-4
Gap md: gap-8
Gap lg: gap-12
```

### Color roles
```
Background primary: bg-[#0E1116]
Background secondary: bg-[#1C2530]
Background accent: bg-[#2F6FED]
Text primary: text-[#F7F5F0]
Text secondary: text-[#F7F5F0]/70
Text muted: text-[#F7F5F0]/45
Button primary: bg-[#2F6FED] text-white
Button secondary: bg-[#1C2530] text-[#F7F5F0] border border-white/10
```

---

## [FORBIDDEN]

These classes are banned in this style. Check for them before returning code:

### Banned classes
- `bg-white`
- `bg-gray-100`
- `bg-gradient-to-r`
- `from-indigo-600`
- `via-purple-600`
- `to-pink-500`
- `rounded-full`
- `duration-1000`

### Banned patterns
- matches `^bg-gradient-`
- matches `^bg-white$`
- matches `^from-(indigo|purple|pink|fuchsia)-`

### Why they are banned
- `bg-white`: Scrollytelling uses a dark data-feature base #0E1116 (or a controlled paper variant)
- `bg-gradient-to-r`: The stage is flat; focus comes from signal-blue highlights, not gradients
- `from-indigo-600`: No AI-cliche gradients; one signal blue and one vermilion accent only
- `rounded-full`: Chart/data UI reads better with restrained rounded-md, not pills
- `duration-1000`: Step transitions stay 0.5-0.8s so the narrative keeps pace

> WARNING: if your code contains any of the above, replace it before shipping.

---

## [REQUIRED]

### Every button must include
```
bg-[#2F6FED] text-white font-semibold
rounded-md
hover:bg-[#2560d4] active:scale-[0.98]
focus:outline-none focus-visible:ring-2 focus-visible:ring-[#2F6FED]/50
transition-all duration-200
```

### Every card must include
```
border-l-2 border-[#2F6FED]
pl-6 py-4
bg-transparent
```

### Every input must include
```
bg-[#1C2530] border border-white/10
rounded-md
text-[#F7F5F0] placeholder-[#F7F5F0]/30
focus:outline-none focus:border-[#2F6FED] focus:ring-2 focus:ring-[#2F6FED]/30
transition-all duration-200
```

---

## [COMPARE] Scrollytelling wrong vs right

The wrong examples below stand for generic library defaults that were never adapted to this style. Do not read them as visual suggestions.

### Button

[WRONG] **Wrong** (generic component library default, do not copy):
```html
<button class="{GENERIC_LIBRARY_BUTTON_DEFAULT}">
 Click me
</button>
```

[CORRECT] **Right** (uses this style's tokens):
```html
<button class="bg-[#2F6FED] text-white font-semibold rounded-md hover:bg-[#2560d4] active:scale-[0.98] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#2F6FED]/50 transition-all duration-200 bg-[#2F6FED] text-white">
 Click me
</button>
```

### Card

[WRONG] **Wrong** (generic card, not adapted to this style):
```html
<div class="{GENERIC_LIBRARY_CARD_DEFAULT}">
 <h3>{TITLE}</h3>
</div>
```

[CORRECT] **Right** (uses this style's card tokens):
```html
<div class="border-l-2 border-[#2F6FED] pl-6 py-4 bg-transparent py-4 pl-6">
 <h3 class="font-bold text-[#F7F5F0] tracking-tight text-xl md:text-2xl">{TITLE}</h3>
</div>
```

### Input

[WRONG] **Wrong** (generic input, not adapted to this style):
```html
<input class="{GENERIC_LIBRARY_INPUT_DEFAULT}" />
```

[CORRECT] **Right** (uses this style's input tokens):
```html
<input class="bg-[#1C2530] border border-white/10 rounded-md text-[#F7F5F0] placeholder-[#F7F5F0]/30 focus:outline-none focus:border-[#2F6FED] focus:ring-2 focus:ring-[#2F6FED]/30 transition-all duration-200" placeholder="{PLACEHOLDER}" />
```

---

## [TEMPLATES] Scrollytelling page skeletons

These skeletons use this style's tokens only. Replace `{PLACEHOLDER}` values, but keep every token in place:

### Navigation
```html
<nav class="bg-[#0E1116] text-[#F7F5F0] border border-white/10 px-6 md:px-8">
 <div class="flex items-center justify-between max-w-6xl mx-auto gap-8">
 <a href="/" class="font-bold text-[#F7F5F0] tracking-tight text-xl md:text-2xl">
 {LOGO_TEXT}
 </a>
 <div class="flex gap-8 text-[#F7F5F0]/70 leading-relaxed text-sm">
 {NAV_LINKS}
 </div>
 </div>
</nav>
```

### Hero section
```html
<section class="bg-[#2F6FED] text-[#F7F5F0] py-20 md:py-28 px-6 md:px-8">
 <div class="max-w-4xl mx-auto">
 <h1 class="font-bold text-[#F7F5F0] tracking-tight text-5xl md:text-7xl">
 {HEADLINE}
 </h1>
 <p class="text-[#F7F5F0]/70 leading-relaxed text-base md:text-lg max-w-xl">
 {SUBHEADLINE}
 </p>
 <button class="bg-[#2F6FED] text-white font-semibold rounded-md hover:bg-[#2560d4] active:scale-[0.98] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#2F6FED]/50 transition-all duration-200 bg-[#2F6FED] text-white">
 {CTA_TEXT}
 </button>
 </div>
</section>
```

### Card grid
```html
<section class="bg-[#0E1116] text-[#F7F5F0] py-20 md:py-28 px-6 md:px-8">
 <div class="max-w-6xl mx-auto">
 <h2 class="font-bold text-[#F7F5F0] tracking-tight text-3xl md:text-4xl">{SECTION_TITLE}</h2>
 <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
 <!-- Card template - repeat for each card -->
 <div class="border-l-2 border-[#2F6FED] pl-6 py-4 bg-transparent py-4 pl-6">
 <h3 class="font-bold text-[#F7F5F0] tracking-tight text-xl md:text-2xl">{CARD_TITLE}</h3>
 <p class="text-[#F7F5F0]/70 leading-relaxed text-base md:text-lg text-[#F7F5F0]/45">{CARD_DESCRIPTION}</p>
 </div>
 </div>
 </div>
</section>
```

### Form input
```html
<input class="bg-[#1C2530] border border-white/10 rounded-md text-[#F7F5F0] placeholder-[#F7F5F0]/30 focus:outline-none focus:border-[#2F6FED] focus:ring-2 focus:ring-[#2F6FED]/30 transition-all duration-200" placeholder="{PLACEHOLDER}" />
```

### Footer
```html
<footer class="bg-[#1C2530] text-[#F7F5F0]/70 py-20 md:py-28 px-6 md:px-8">
 <div class="max-w-6xl mx-auto">
 <div class="grid grid-cols-1 md:grid-cols-3 gap-12">
 <div>
 <span class="font-bold text-[#F7F5F0] tracking-tight text-xl md:text-2xl">{LOGO_TEXT}</span>
 <p class="text-[#F7F5F0]/70 leading-relaxed text-sm">{TAGLINE}</p>
 </div>
 <div>
 <h4 class="font-bold text-[#F7F5F0] tracking-tight text-xl md:text-2xl">{COLUMN_TITLE}</h4>
 <ul class="text-[#F7F5F0]/70 leading-relaxed text-sm">
 {FOOTER_LINKS}
 </ul>
 </div>
 </div>
 </div>
</footer>
```

---

## [CHECKLIST] Scrollytelling post-generation self check

**Before returning code, verify every token and rule below. Fix any violation before delivering:**

### Token check
- [ ] Button includes: `bg-[#2F6FED] text-white font-semibold rounded-md hover:bg-[#2560d4] active:scale-[0.98] focus:outline-none focus-visible:ring-2 focus-visible:ring-[#2F6FED]/50 transition-all duration-200`
- [ ] Card includes: `border-l-2 border-[#2F6FED] pl-6 py-4 bg-transparent`
- [ ] Input includes: `bg-[#1C2530] border border-white/10 rounded-md text-[#F7F5F0] placeholder-[#F7F5F0]/30 focus:outline-none focus:border-[#2F6FED] focus:ring-2 focus:ring-[#2F6FED]/30 transition-all duration-200`

### Forbidden check
- [ ] Not using `bg-white`
- [ ] Not using `bg-gray-100`
- [ ] Not using `bg-gradient-to-r`
- [ ] Not using `from-indigo-600`
- [ ] Not using `via-purple-600`
- [ ] Not using `to-pink-500`
- [ ] Not using `rounded-full`
- [ ] Not using `duration-1000`

### Style rule check
- [ ] 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

### Style drift check
- [ ] Does not violate: Never read offsetTop/getBoundingClientRect in a scroll handler for continuous scrub (jank) — trigger discrete states with IntersectionObserver
- [ ] Does not violate: Never cram multiple new facts into one step (narrative tension collapses)
- [ ] Does not violate: Never animate layout properties on canvas states (top/width/height) — transform/opacity only
- [ ] Does not violate: Never let a count-up animation be the only source of a number (screen readers / SEO can't read it)
- [ ] Does not violate: Never let the canvas crowd out text on mobile

### Delivery check
- [ ] Responsive layout holds on phone, tablet and desktop with no horizontal overflow
- [ ] Every interactive element has a visible focus state, an accessible name and a reduced-motion path
- [ ] Text contrast meets WCAG AA and colour alone never carries state
- [ ] The result is still recognizable at a glance as Scrollytelling

---

## [EXAMPLES] Example prompts

### 1. 

```
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
```

### 2. 

```
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
```

## Absolute Bans (Match and Refuse)

If any of the following patterns appear, it is a style violation — rewrite without exception.

- read offsetTop/getBoundingClientRect in a scroll handler for continuous scrub (jank) — trigger discrete states with IntersectionObserver
- cram multiple new facts into one step (narrative tension collapses)
- animate layout properties on canvas states (top/width/height) — transform/opacity only
- let a count-up animation be the only source of a number (screen readers / SEO can't read it)
- let the canvas crowd out text on mobile
- use more than two accent colors
- omit the prefers-reduced-motion instant fallback

## Self-Check (Verify Before Shipping)

If any item fails, the style has drifted — fix before shipping.

- [ ] No purple-to-blue gradients
- [ ] No overused fonts (Inter, Roboto, Geist, Fraunces, Plus Jakarta Sans)
- [ ] No nested cards (cards inside cards)
- [ ] No gray text on colored backgrounds
- [ ] Body text contrast meets WCAG AA (>= 4.5:1)
- [ ] No bounce or elastic easing curves
- [ ] Animations have a prefers-reduced-motion fallback
- [ ] Body text line length capped at 65-75 characters
- [ ] No side-stripe accent borders (border-left/right > 1px)
- [ ] No gradient text (background-clip: text)
- [ ] No glassmorphism used as the default surface treatment
- [ ] No tiny uppercase tracked eyebrow labels above every section heading
- [ ] 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

Community

Ratings & Feedback

No ratings yet - be the firstSign in to rate
0 comments
Sign in to post a comment. Sign In

Component Templates

Component Preview

Button

Signal-blue step button with focus ring

Frontend Readiness

Dark Mode, States, Motion, and Accessibility

This layer tracks whether the style is ready for real websites: theme modes, state feedback, keyboard access, and performance constraints.

Overall

51%

Fallback

Dark Mode

0%

Missing

UI States

79%

Partial

Motion

70%

Partial

A11y

70%

Partial

Performance

35%

Fallback

Key State Coverage

light
HoverFocus VisibleDisabledLoadingEmptyErrorSuccess

Button

Default / Hover / Focus Visible / Active / Disabled

Input

Default / Hover / Focus Visible / Disabled / Error

Card

Default / Hover / Focus Visible / Loading / Skeleton

Form

Default / Focus Visible / Disabled / Loading / Error

Implementation Notes

  • No curated dark-mode contract exists yet.
  • Check contrast in both light and dark modes.
  • Check heavy shadows, blur, large media, and scroll-linked effects manually.
  • No style-specific performance cost profile has been curated yet.

FAQ

Scrollytelling — Frequently Asked Questions

01What is the Scrollytelling design style?
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. Its core principle: The scrollytelling creed: scrolling is playback, not browsing.
02What colors does Scrollytelling use?
Scrollytelling uses #0E1116 as its primary color and #F7F5F0 as its secondary color, with accent colors #2F6FED, #E8503A, #1C2530. Every hex value can be copied from the palette on this page.
03How do I apply Scrollytelling correctly?
Key practices: 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.
04What are common mistakes with Scrollytelling?
Avoid: 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.
05How do I prompt AI to generate Scrollytelling UI?
Copy the ready-made AI prompt on this page — it bundles the design tokens and component rules — and paste it into ChatGPT, Claude, Claude Code, or Codex. Effective prompts include the keywords: scrollytelling, scroll-driven storytelling, scroll animation, sticky scroll.
Global Styles

Global CSS

css
/* Scrollytelling Global Styles */

:root {
  --st-base: #0E1116;
  --st-surface: #1C2530;
  --st-paper: #F7F5F0;
  --st-signal: #2F6FED;
  --st-alert: #E8503A;
  --st-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* The pinned stage */
.st-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Step narration blocks — give each room to breathe */
.st-step {
  min-height: 80vh;
  display: flex;
  align-items: center;
}

/* Canvas layers cross-fade / slide between discrete states (transform+opacity only) */
.st-layer {
  transition: opacity 0.6s var(--st-ease), transform 0.6s var(--st-ease);
  will-change: opacity, transform;
}
.st-layer[data-active="false"] { opacity: 0; }
.st-layer[data-active="true"] { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .st-layer { transition: none; }
}

IDE Integration

IDE Configuration Export

Download configuration files for AI coding assistants to generate code in this style.

Style Pack

Export Style Pack

Get complete machine-readable style assets including design tokens, Tailwind presets, CSS variables, and shadcn/ui themes.

Metadata

Style metadata including version information

Design Tokens

Compatible with Figma / Style Dictionary / Tokens Studio

Tailwind Preset

Tailwind CSS theme preset, import directly in config

Global CSS

CSS variables and base styles

shadcn Theme

shadcn/ui theme configuration

CSS Variables

Pure CSS variables, works with any project

SKILL.md

Loadable skill pack for Cursor / Claude Code / VS Code

Design 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.

Accessibility

Accessibility Score

WCAG 2.1 compliance analysis based on color contrast and typography readability.

79

Overall Score

Grade: B - Good

Contrast Ratios

Score: 76/100Average Ratio: 11.67:1
AA FailAAA Fail
ContextColorsRatioAAAAA
Text on background
/#F7F5F0 / #0E1116
17.36:1
Secondary text on background
/#F7F5F0 / #0E1116
17.36:1
Muted text on background
/#F7F5F0 / #0E1116
17.36:1
Text on secondary background
/#F7F5F0 / #1C2530
14.21:1
Secondary text on secondary
/#F7F5F0 / #1C2530
14.21:1
Button primary
/#ffffff / #2F6FED
4.55:1
Text on accent 1
/#F7F5F0 / #2F6FED
4.17:1
Alt text on accent 1
/#F7F5F0 / #2F6FED
4.17:1

Readability

Score

87/100

Font Size

text-base md:text-lg

Font Weight

font-bold text-[#F7F5F0] tracking-tight

Line Height

default

Scoring is based on WCAG 2.1 standards. AA requires 4.5:1 contrast for normal text, 3:1 for large text; AAA requires 7:1 for normal text, 4.5:1 for large text.

StyleKit

A curated web design style library to help AI generate better-looking websites.

FeedbackSupport Maintenance
Explore+
StylesTemplatesAnimationsResource LibraryPromptsCollectionsCommunity
Learn+
FoundationsGuideRecipesChangelogBlog
Trust+
AboutContactPrivacyTermsSupport UsRefunds

(c) 2026 StyleKit. Open source project.

闄旾CP澶?025065501鍙?3

Colophon/SK — 2026

Built with the Editorial style

01FeedbackTell us what could work better02Support MaintenanceIf StyleKit helps your workflow, voluntary support helps cover servers, domains, and ongoing upkeep.

StyleKit

A curated web design style library to help AI generate better-looking websites.

GitHub Repository

Explore

StylesTemplatesAnimationsResource LibraryPromptsCollectionsCommunity

Learn

FoundationsGuideRecipesChangelogBlog

Dispatch

Get notified when new styles, templates and tools ship.

Stay Updated

By subscribing, you agree to our Privacy Policy and Terms.

(c) 2026 StyleKit. Open source project.

AboutContactPrivacyTermsSupport UsRefunds/陕ICP备2025065501号-3

StyleKit